diff --git a/SAS/TMSS/frontend/frontend_poc/src/UC1.js b/SAS/TMSS/frontend/frontend_poc/src/UC1.js index f01fd526d11fef0c2f68cfaf6425da904eb1bb7f..c6cc5caf66d7eb91e82d72a92d5ab4ed7ac28e43 100644 --- a/SAS/TMSS/frontend/frontend_poc/src/UC1.js +++ b/SAS/TMSS/frontend/frontend_poc/src/UC1.js @@ -187,7 +187,7 @@ class EditTaskDraft extends Component { copy_reason: null, scheduling_unit_draft: api_url + "scheduling_unit_draft/1/", specifications_template: api_url + "task_template/1/", - related_task_blueprint: [], + task_blueprints: [], produced_by: [], consumed_by: [] } @@ -268,7 +268,7 @@ class EditTaskDraft extends Component { copy_reason: this.state.copy_reason, scheduling_unit_draft: this.state.scheduling_unit_draft, specifications_template: this.state.specifications_template, - related_task_blueprint: this.state.related_task_blueprint, + task_blueprints: this.state.task_blueprints, produced_by: this.state.produced_by, consumed_by: this.state.consumed_by }; diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py index 1b60d3a41dd92920610c7e48a673ec78553fe2d1..819a8a25ed762d1782e9a434b629f414e727d742 100644 --- a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py +++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py @@ -762,7 +762,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='taskblueprint', name='draft', - field=models.ForeignKey(help_text='Task Draft which this task instantiates.', on_delete=django.db.models.deletion.CASCADE, related_name='related_task_blueprint', to='tmssapp.TaskDraft'), + field=models.ForeignKey(help_text='Task Draft which this task instantiates.', on_delete=django.db.models.deletion.CASCADE, related_name='task_blueprints', to='tmssapp.TaskDraft'), ), migrations.AddField( model_name='taskblueprint', @@ -890,7 +890,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='schedulingunitblueprint', name='draft', - field=models.ForeignKey(help_text='Scheduling Unit Draft which this run instantiates.', on_delete=django.db.models.deletion.CASCADE, related_name='related_scheduling_unit_blueprint', to='tmssapp.SchedulingUnitDraft'), + field=models.ForeignKey(help_text='Scheduling Unit Draft which this run instantiates.', on_delete=django.db.models.deletion.CASCADE, related_name='scheduling_unit_blueprints', to='tmssapp.SchedulingUnitDraft'), ), migrations.AddField( model_name='schedulingunitblueprint', diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py index c9d82dfdd5409ebab73f94ed7d168f5134b4a38e..2956c3e09c58785fe0948e66f72a0cce770d5ad0 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py @@ -273,7 +273,7 @@ class SchedulingUnitBlueprint(NamedCommon): requirements_doc = JSONField(help_text='Scheduling and/or quality requirements for this scheduling unit (IMMUTABLE).') do_cancel = BooleanField() requirements_template = ForeignKey('SchedulingUnitTemplate', on_delete=CASCADE, help_text='Schema used for requirements_doc (IMMUTABLE).') - draft = ForeignKey('SchedulingUnitDraft', related_name='related_scheduling_unit_blueprint', on_delete=CASCADE, help_text='Scheduling Unit Draft which this run instantiates.') + draft = ForeignKey('SchedulingUnitDraft', related_name='scheduling_unit_blueprints', on_delete=CASCADE, help_text='Scheduling Unit Draft which this run instantiates.') def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if self.requirements_doc and self.requirements_template_id and self.requirements_template.schema: @@ -320,8 +320,8 @@ class TaskBlueprint(NamedCommon): specifications_doc = JSONField(help_text='Schedulings for this task (IMMUTABLE).') do_cancel = BooleanField(help_text='Cancel this task.') specifications_template = ForeignKey('TaskTemplate', on_delete=CASCADE, help_text='Schema used for specifications_doc (IMMUTABLE).') - draft = ForeignKey('TaskDraft', related_name='related_task_blueprint', on_delete=CASCADE, help_text='Task Draft which this task instantiates.') - scheduling_unit_blueprint = ForeignKey('SchedulingUnitBlueprint', on_delete=CASCADE, help_text='Scheduling Unit Blueprint to which this task belongs.') + draft = ForeignKey('TaskDraft', related_name='task_blueprints', on_delete=CASCADE, help_text='Task Draft which this task instantiates.') + scheduling_unit_blueprint = ForeignKey('SchedulingUnitBlueprint', related_name='task_blueprints', on_delete=CASCADE, help_text='Scheduling Unit Blueprint to which this task belongs.') def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if self.specifications_doc and self.specifications_template_id and self.specifications_template.schema: diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py index c73db06fb64755478f8eb45ac53e4cb72c8023b4..a745c8e18e36f4995e1e2a75020b5d4142010258 100644 --- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py @@ -179,10 +179,10 @@ class SchedulingUnitDraftSerializer(RelationalHyperlinkedModelSerializer): class Meta: model = models.SchedulingUnitDraft fields = '__all__' - extra_fields = ['related_scheduling_unit_blueprint', 'task_drafts'] + extra_fields = ['scheduling_unit_blueprints', 'task_drafts'] -class SchedulingUnitBlueprintSerializer(serializers.HyperlinkedModelSerializer): +class SchedulingUnitBlueprintSerializer(RelationalHyperlinkedModelSerializer): # Create a JSON editor form to replace the simple text field based on the schema in the template that this # draft refers to. If that fails, the JSONField remains a standard text input. @@ -196,6 +196,7 @@ class SchedulingUnitBlueprintSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = models.SchedulingUnitBlueprint fields = '__all__' + extra_fields = ['task_blueprints'] class TaskDraftSerializer(RelationalHyperlinkedModelSerializer): @@ -212,7 +213,7 @@ class TaskDraftSerializer(RelationalHyperlinkedModelSerializer): class Meta: model = models.TaskDraft fields = '__all__' - extra_fields = ['related_task_blueprint', 'produced_by', 'consumed_by'] + extra_fields = ['task_blueprints', 'produced_by', 'consumed_by'] class TaskBlueprintSerializer(RelationalHyperlinkedModelSerializer): diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py index da8de9468d9b8663e862a92b184eb1594fd02e70..dfdbc87f11dd46a29e74537bd8d0e625e6044c70 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py @@ -22,8 +22,8 @@ from lofar.sas.tmss.tmss.tmssapp import serializers from datetime import datetime from lofar.common.json_utils import get_default_json_object_for_schema from lofar.common.datetimeutils import formatDatetime -from lofar.sas.tmss.tmss.tmssapp.tasks import create_scheduling_unit_blueprint_from_scheduling_unit_draft, create_task_blueprint_from_task_draft -from lofar.sas.tmss.tmss.tmssapp.subtasks import create_subtasks_from_task_blueprint +from lofar.sas.tmss.tmss.tmssapp.tasks import * +from lofar.sas.tmss.tmss.tmssapp.subtasks import * @@ -169,7 +169,7 @@ class SchedulingUnitDraftViewSet(LOFARViewSet): @swagger_auto_schema(responses={201: 'Created schduling unit blueprint, see Location in Response header', 403: 'forbidden'}, operation_description="Carve this scheduling unit draft in stone, and make an (uneditable) blueprint out of it.") - @action(methods=['get'], detail=True, url_name="create_scheduling_unit_blueprint") + @action(methods=['get'], detail=True, url_name="create_scheduling_unit_blueprint", name="Create Blueprint") def create_scheduling_unit_blueprint(self, request, pk=None): scheduling_unit_draft = get_object_or_404(models.SchedulingUnitDraft, pk=pk) scheduling_unit_blueprint = create_scheduling_unit_blueprint_from_scheduling_unit_draft(scheduling_unit_draft) @@ -184,6 +184,23 @@ class SchedulingUnitDraftViewSet(LOFARViewSet): status=status.HTTP_201_CREATED, headers={'Location': scheduling_unit_blueprint_path}) + @swagger_auto_schema(responses={201: 'Created schduling unit blueprint, see Location in Response header', + 403: 'forbidden'}, + operation_description="Carve this scheduling unit draft, and its tasks in stone, and make blueprint(s) out of it, create subtasks, and schedule the ones that are not dependend on predecessors.") + @action(methods=['get'], detail=True, url_name="create_blueprints_and_schedule", name="Create Blueprints and Schedule") + def create_blueprints_and_schedule(self, request, pk=None): + scheduling_unit_draft = get_object_or_404(models.SchedulingUnitDraft, pk=pk) + scheduling_unit_blueprint = create_task_blueprints_and_subtasks_and_schedule_subtasks_from_scheduling_unit_draft(scheduling_unit_draft) + + # url path magic to construct the new scheduling_unit_blueprint_path url + scheduling_unit_draft_path = request._request.path + base_path = scheduling_unit_draft_path[:scheduling_unit_draft_path.find('/scheduling_unit_draft')] + scheduling_unit_blueprint_path = '%s/scheduling_unit_blueprint/%s/' % (base_path, scheduling_unit_blueprint.id,) + + # return a response with the new serialized SchedulingUnitBlueprintSerializer, and a Location to the new instance in the header + return Response(serializers.SchedulingUnitBlueprintSerializer(scheduling_unit_blueprint, context={'request':request}).data, + status=status.HTTP_201_CREATED, + headers={'Location': scheduling_unit_blueprint_path}) class SchedulingUnitDraftNestedViewSet(LOFARNestedViewSet): queryset = models.SchedulingUnitDraft.objects.all() @@ -273,10 +290,10 @@ class TaskBlueprintViewSet(LOFARViewSet): queryset = models.TaskBlueprint.objects.all() serializer_class = serializers.TaskBlueprintSerializer - @swagger_auto_schema(responses={200: 'Create subtasks from this task blueprint', + @swagger_auto_schema(responses={200: 'The created subtasks', 403: 'forbidden'}, operation_description="Create subtasks from this task blueprint") - @action(methods=['get'], detail=True) + @action(methods=['get'], detail=True, url_path='create_subtasks') def create_subtasks(self, request, pk=None): task_blueprint = get_object_or_404(models.TaskBlueprint, pk=pk) subtasks = create_subtasks_from_task_blueprint(task_blueprint) @@ -285,6 +302,22 @@ class TaskBlueprintViewSet(LOFARViewSet): serialized_subtasks = serializers.SubtaskSerializer(subtasks, many=True, context={'request':request}).data return Response(serialized_subtasks, status=status.HTTP_201_CREATED) + @swagger_auto_schema(responses={200: 'The created subtasks', + 403: 'forbidden'}, + operation_description="Create subtasks from this task blueprint, and schedule the ones that do not depend on predecessors.") + @action(methods=['get'], detail=True, url_path='create_and_schedule_subtasks') + def create_and_schedule_subtasks(self, request, pk=None): + task_blueprint = get_object_or_404(models.TaskBlueprint, pk=pk) + subtasks = create_subtasks_from_task_blueprint(task_blueprint) + + for subtask in subtasks: + if len(subtask.predecessors.all()) == 0: + schedule_subtask(subtask) + + # return a response with the new serialized Subtasks + serialized_subtasks = serializers.SubtaskSerializer(subtasks, many=True, context={'request':request}).data + return Response(serialized_subtasks, status=status.HTTP_201_CREATED) + @swagger_auto_schema(responses={200: 'The predecessor task draft of this task draft', 403: 'forbidden'}, operation_description="Get the predecessor task draft of this task draft.") @@ -313,7 +346,7 @@ class TaskBlueprintNestedViewSet(LOFARNestedViewSet): def get_queryset(self): if 'task_draft_id' in self.kwargs: task_draft = get_object_or_404(models.TaskDraft, pk=self.kwargs['task_draft_id']) - return task_draft.related_task_blueprint.all() + return task_draft.task_blueprints.all() else: return models.TaskBlueprint.objects.all() diff --git a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py index e429af69697f98adec22c5db3a46e5781c2f72a2..1f8bac781f597bc7887786d88e4e3192894bdb98 100755 --- a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py +++ b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py @@ -1198,7 +1198,7 @@ class SchedulingUnitDraftTestCase(unittest.TestCase): scheduling_unit_blueprint_2.save() # assert response_data = GET_OK_and_assert_equal_expected_response(self, BASE_URL + '/scheduling_unit_draft/%s/' % scheduling_unit_draft.id, test_data_1) - assertUrlList(self, response_data['related_scheduling_unit_blueprint'], [scheduling_unit_blueprint_1, scheduling_unit_blueprint_2]) + assertUrlList(self, response_data['scheduling_unit_blueprints'], [scheduling_unit_blueprint_1, scheduling_unit_blueprint_2]) def test_SchedulingUnitDraft_contains_list_of_related_TaskDraft(self): @@ -1383,7 +1383,7 @@ class TaskDraftTestCase(unittest.TestCase): task_blueprint_2.save() # assert response_data = GET_OK_and_assert_equal_expected_response(self, BASE_URL + '/task_draft/%s/' % task_draft.id, test_data_1) - assertUrlList(self, response_data['related_task_blueprint'], [task_blueprint_1, task_blueprint_2]) + assertUrlList(self, response_data['task_blueprints'], [task_blueprint_1, task_blueprint_2]) def test_TaskDraft_contains_lists_of_related_TaskRelationDraft(self): diff --git a/SAS/TMSS/test/tmss_test_data_rest.py b/SAS/TMSS/test/tmss_test_data_rest.py index 780dda23703ee71ca5d6c291f6cdc12bc75e3401..06ee8fad28ce99760db2debeeb28993c232f5fcf 100644 --- a/SAS/TMSS/test/tmss_test_data_rest.py +++ b/SAS/TMSS/test/tmss_test_data_rest.py @@ -190,7 +190,7 @@ class TMSSRESTTestDataCreator(): "copies": None, "scheduling_set": scheduling_set_url, "requirements_template": template_url, - "related_scheduling_unit_blueprint": [], + "scheduling_unit_blueprints": [], "task_drafts": []} def TaskDraft(self, name='my_task_draft', scheduling_unit_draft_url=None, template_url=None): @@ -208,7 +208,7 @@ class TMSSRESTTestDataCreator(): "copies": None, "scheduling_unit_draft": scheduling_unit_draft_url, "specifications_template": template_url, - 'related_task_blueprint': [], + 'task_blueprints': [], 'produced_by': [], 'consumed_by': []}