From a8481d29a183ff5cd656b515d27073cc2eaca584 Mon Sep 17 00:00:00 2001
From: Jorrit Schaap <schaap@astron.nl>
Date: Fri, 4 Dec 2020 10:29:11 +0100
Subject: [PATCH] TMSS-320: use UniqueConstraint to ensure that no duplicate
 blueprints are created

---
 .../tmss/tmssapp/migrations/0001_initial.py   |  6 ++-
 .../src/tmss/tmssapp/models/specification.py  |  6 ++-
 SAS/TMSS/src/tmss/tmssapp/tasks.py            | 51 +++++++++++--------
 3 files changed, 40 insertions(+), 23 deletions(-)

diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py
index 65b22672362..3ef33b5ef6b 100644
--- a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py
+++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.0.9 on 2020-12-04 09:03
+# Generated by Django 3.0.9 on 2020-12-04 09:22
 
 from django.conf import settings
 import django.contrib.postgres.fields
@@ -1377,6 +1377,10 @@ class Migration(migrations.Migration):
             model_name='taskblueprint',
             constraint=models.UniqueConstraint(fields=('name', 'scheduling_unit_blueprint'), name='TaskBlueprint_unique_name_in_scheduling_unit'),
         ),
+        migrations.AddConstraint(
+            model_name='taskblueprint',
+            constraint=models.UniqueConstraint(fields=('draft', 'scheduling_unit_blueprint'), name='TaskBlueprint_unique_from_task_draft_in_scheduling_unit'),
+        ),
         migrations.AddConstraint(
             model_name='subtasktemplate',
             constraint=models.UniqueConstraint(fields=('name', 'version'), name='subtasktemplate_unique_name_version'),
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
index f743f4ab36e..247020bd672 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
@@ -900,8 +900,10 @@ class TaskBlueprint(NamedCommon):
     scheduling_unit_blueprint = ForeignKey('SchedulingUnitBlueprint', related_name='task_blueprints', on_delete=CASCADE, help_text='Scheduling Unit Blueprint to which this task belongs.')
 
     class Meta:
-        # ensure there are no tasks with duplicate names within one scheduling_unit
-        constraints = [UniqueConstraint(fields=['name', 'scheduling_unit_blueprint'], name='TaskBlueprint_unique_name_in_scheduling_unit')]
+        # ensure there are no tasks with duplicate names within one scheduling_unit,
+        # and ensure there are no taskblueprints created from the same taskdraft within this scheduling_unit_blueprint
+        constraints = [UniqueConstraint(fields=['name', 'scheduling_unit_blueprint'], name='TaskBlueprint_unique_name_in_scheduling_unit'),
+                       UniqueConstraint(fields=['draft', 'scheduling_unit_blueprint'], name='TaskBlueprint_unique_from_task_draft_in_scheduling_unit')]
 
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
         annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template')
diff --git a/SAS/TMSS/src/tmss/tmssapp/tasks.py b/SAS/TMSS/src/tmss/tmssapp/tasks.py
index 30d26fbc129..10d7b4dfcf4 100644
--- a/SAS/TMSS/src/tmss/tmssapp/tasks.py
+++ b/SAS/TMSS/src/tmss/tmssapp/tasks.py
@@ -236,16 +236,23 @@ def create_task_blueprint_from_task_draft(task_draft: models.TaskDraft) -> model
     if scheduling_unit_blueprint is None:
         scheduling_unit_blueprint = create_scheduling_unit_blueprint_from_scheduling_unit_draft(task_draft.scheduling_unit_draft)
 
-    task_blueprint = TaskBlueprint.objects.create(
-        description=task_draft.description,
-        name=task_draft.name,
-        do_cancel=False,
-        draft=task_draft,
-        scheduling_unit_blueprint=scheduling_unit_blueprint,
-        specifications_doc=task_draft.specifications_doc,
-        specifications_template=task_draft.specifications_template)
-
-    logger.info("created task_blueprint id=%s from task_draft id=%s", task_blueprint.pk, task_draft.pk)
+    try:
+        task_blueprint = TaskBlueprint.objects.create(
+            description=task_draft.description,
+            name=task_draft.name,
+            do_cancel=False,
+            draft=task_draft,
+            scheduling_unit_blueprint=scheduling_unit_blueprint,
+            specifications_doc=task_draft.specifications_doc,
+            specifications_template=task_draft.specifications_template)
+
+        logger.info("created task_blueprint id=%s from task_draft id=%s", task_blueprint.pk, task_draft.pk)
+    except IntegrityError as e:
+        if 'TaskBlueprint_unique_name_in_scheduling_unit' in str(e) or 'TaskBlueprint_unique_from_task_draft_in_scheduling_unit' in str(e):
+            logger.info("task_blueprint from task_draft id=%s already exists in scheduling_unit_blueprint id=%s name='%s'",
+                        task_draft.pk, scheduling_unit_blueprint.id, scheduling_unit_blueprint.name)
+        else:
+            raise
 
     # now that we have a task_blueprint, its time to refresh the task_draft so we get the non-cached fields
     task_draft.refresh_from_db()
@@ -260,10 +267,6 @@ def create_task_blueprint_from_task_draft(task_draft: models.TaskDraft) -> model
         for producing_task_blueprint in task_relation_draft.producer.task_blueprints.all():
             for consuming_task_blueprint in task_relation_draft.consumer.task_blueprints.all():
                 try:
-                    # do nothing if task_relation_blueprint already exists...
-                    models.TaskRelationBlueprint.objects.get(producer_id=producing_task_blueprint.id, consumer_id=consuming_task_blueprint.id)
-                except models.TaskRelationBlueprint.DoesNotExist:
-                    # ...'else' create it.
                     task_relation_blueprint = models.TaskRelationBlueprint.objects.create(draft=task_relation_draft,
                                                                                           input_role=task_relation_draft.input_role,
                                                                                           output_role=task_relation_draft.output_role,
@@ -273,7 +276,14 @@ def create_task_blueprint_from_task_draft(task_draft: models.TaskDraft) -> model
                                                                                           selection_template=task_relation_draft.selection_template,
                                                                                           dataformat=task_relation_draft.dataformat)
                     logger.info("created task_relation_blueprint id=%s which connects task_blueprints producer_id=%s and consumer_id=%s",
-                                task_relation_blueprint.pk, producing_task_blueprint.pk, consuming_task_blueprint.pk,)
+                                task_relation_blueprint.pk, producing_task_blueprint.pk, consuming_task_blueprint.pk)
+                except IntegrityError as e:
+                    if 'TaskRelationBlueprint_unique_relation' in str(e):
+                        logger.info("task_relation_blueprint with producer_id=%s and consumer_id=%s already exists",
+                                    producing_task_blueprint.pk, consuming_task_blueprint.pk)
+                    else:
+                        raise
+
 
     # Do the same 'trick' for Task Scheduling Relation Draft to Blueprint
     task_draft_scheduling_relations = list(task_draft.first_scheduling_relation.all()) + list(task_draft.second_scheduling_relation.all())
@@ -281,17 +291,18 @@ def create_task_blueprint_from_task_draft(task_draft: models.TaskDraft) -> model
         for first_task_blueprint in task_scheduling_relation_draft.first.task_blueprints.all():
             for second_task_blueprint in task_scheduling_relation_draft.second.task_blueprints.all():
                 try:
-                    # do nothing if task_scheduling_relation_blueprint already exists...
-                    models.TaskSchedulingRelationBlueprint.objects.get(first_id=first_task_blueprint.id,
-                                                                       second_id=second_task_blueprint.id)
-                except models.TaskSchedulingRelationBlueprint.DoesNotExist:
-                    # ...'else' create it.
                     task_scheduling_relation_blueprint = models.TaskSchedulingRelationBlueprint.objects.create(first=first_task_blueprint,
                                                                                                                second=second_task_blueprint,
                                                                                                                time_offset=task_scheduling_relation_draft.time_offset,
                                                                                                                placement=task_scheduling_relation_draft.placement)
                     logger.info("created task_scheduling_relation_blueprint id=%s which connects task_blueprints first_id=%s and second_id=%s, placement=%s time_offset=%s[sec]",
                                 task_scheduling_relation_blueprint.pk, first_task_blueprint.pk, second_task_blueprint.pk, task_scheduling_relation_draft.placement, task_scheduling_relation_draft.time_offset)
+                except IntegrityError as e:
+                    if 'TaskSchedulingRelationBlueprint_unique_relation' in str(e):
+                        logger.info("task_scheduling_relation_blueprint with producer_id=%s and consumer_id=%s already exists",
+                                    producing_task_blueprint.pk, consuming_task_blueprint.pk)
+                    else:
+                        raise
 
     return task_blueprint
 
-- 
GitLab