diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py index b04ecd0b93b65653a7426a081eb419c03ddef895..81e15db2fa25ddd0d68245ed2d78273a47ccf76e 100644 --- a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py +++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py @@ -690,6 +690,16 @@ class Migration(migrations.Migration): ('validation_code_js', models.CharField(help_text='JavaScript code for additional (complex) validation.', max_length=128)), ], ), + migrations.CreateModel( + name='TaskType', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( name='Setting', fields=[ @@ -707,6 +717,11 @@ class Migration(migrations.Migration): model_name='tasktemplate', constraint=models.UniqueConstraint(fields=('name', 'version'), name='TaskTemplate_unique_name_version'), ), + migrations.AddField( + model_name='tasktemplate', + name='type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.TaskType'), + ), migrations.AddField( model_name='taskschedulingrelationdraft', name='first', diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py index 60b95bb0dff878b3b8eaa70606b9ad551eedcc71..26683b2cde8f68b1bb5944f8696d0aa09b905f30 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py @@ -187,6 +187,17 @@ class ProjectCategory(AbstractChoice): TEST = "test" +class TaskType(AbstractChoice): + """Defines the model and predefined list of possible TaskType's for Task. + The items in the Choices class below are automagically populated into the database via a data migration.""" + class Choices(Enum): + OBSERVATION = "observation" + PIPELINE = "pipeline" + INGEST = "ingest" + MAINTENANCE = "maintenance" + OTHER = 'other' + + # concrete models class Setting(BasicCommon): @@ -199,7 +210,7 @@ class TaskConnectorType(BasicCommon): datatype = ForeignKey('Datatype', null=False, on_delete=PROTECT) dataformats = ManyToManyField('Dataformat', blank=True) output_of = ForeignKey("TaskTemplate", related_name='output_connector_types', on_delete=CASCADE) - input_of = ForeignKey("TaskTemplate", related_name='inpput_connector_types', on_delete=CASCADE) + input_of = ForeignKey("TaskTemplate", related_name='input_connector_types', on_delete=CASCADE) # @@ -245,6 +256,7 @@ class DefaultSchedulingUnitTemplate(BasicCommon): class TaskTemplate(Template): validation_code_js = CharField(max_length=128, help_text='JavaScript code for additional (complex) validation.') + type = ForeignKey('TaskType', null=False, on_delete=PROTECT) class Meta: # TODO: move up to the abstract base class and replace with django 3.0 UniqueConstraint(... name='%*class)s_unique_name_version) diff --git a/SAS/TMSS/src/tmss/tmssapp/populate.py b/SAS/TMSS/src/tmss/tmssapp/populate.py index c2ebe1653cebb47ded5a4fde25114d5ea533edeb..8de5dc5f2d0bee9de048b37ae94630d3006a83cf 100644 --- a/SAS/TMSS/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/populate.py @@ -37,7 +37,7 @@ def populate_choices(apps, schema_editor): ''' for choice_class in [Role, Datatype, Dataformat, CopyReason, SubtaskState, SubtaskType, StationType, Algorithm, ScheduleMethod, SchedulingRelationPlacement, - Flag, ProjectCategory, PeriodCategory, Quantity]: + Flag, ProjectCategory, PeriodCategory, Quantity, TaskType]: choice_class.objects.bulk_create([choice_class(value=x.value) for x in choice_class.Choices]) def populate_settings(apps, schema_editor): @@ -272,6 +272,7 @@ def _populate_observation_with_stations_schema(): with open(os.path.join(working_dir, "schemas/task-observation-with-stations.json")) as json_file: json_data = json.loads(json_file.read()) task_template_data = {"name": "observation schema", + "type": TaskType.objects.get(value='observation'), "description": 'schema for observations', "version": '0.1', "tags": [], @@ -283,6 +284,7 @@ def _populate_calibrator_addon_schema(): with open(os.path.join(working_dir, "schemas/task-calibrator-addon.json")) as json_file: json_data = json.loads(json_file.read()) task_template_data = {"name": "calibrator schema", + "type": TaskType.objects.get(value='observation'), "description": 'addon schema for calibrator observations', "version": '0.1', "tags": [], @@ -414,6 +416,7 @@ def _populate_preprocessing_schema(): with open(os.path.join(working_dir, "schemas/task-preprocessing.json")) as json_file: json_data = json.loads(json_file.read()) task_template_data = {"name": "preprocessing schema", + "type": TaskType.objects.get(value='pipeline'), "description": 'preprocessing settings', "version": '0.1', "tags": [], diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py index f034bc0acc0376ba2725c25c28f04e5f4ce56d90..0e173fff9865a917e28757e03e1bd8cb0ecd6e52 100644 --- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py @@ -380,5 +380,8 @@ class TaskSchedulingRelationBlueprintSerializer(RelationalHyperlinkedModelSerial fields = '__all__' - +class TaskTypeSerializer(RelationalHyperlinkedModelSerializer): + class Meta: + model = models.TaskType + fields = '__all__' diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py index aa83ab8741ce9925bea917e6023c8af8bf8d3204..4404f40d6265ef41461d8c6db5f2ee114c0e2f03 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py @@ -605,3 +605,8 @@ class TaskRelationBlueprintNestedViewSet(LOFARNestedViewSet): return task_relation_draft.related_task_relation_blueprint.all() else: return models.TaskRelationBlueprint.objects.all() + + +class TaskTypeViewSet(LOFARViewSet): + queryset = models.TaskType.objects.all() + serializer_class = serializers.TaskTypeSerializer \ No newline at end of file diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py index 6f9ea2167eb13acb6c3bf8dc330a07fe806881a2..35696e914d1a97e0b128ec796ad411c9f0b2cf36 100644 --- a/SAS/TMSS/src/tmss/urls.py +++ b/SAS/TMSS/src/tmss/urls.py @@ -90,6 +90,7 @@ router.register(r'flag', viewsets.FlagViewSet) router.register(r'period_category', viewsets.PeriodCategoryViewSet) router.register(r'project_category', viewsets.ProjectCategoryViewSet) router.register(r'quantity', viewsets.QuantityViewSet) +router.register(r'task_type', viewsets.TaskTypeViewSet) # templates router.register(r'generator_template', viewsets.GeneratorTemplateViewSet) diff --git a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py index bc95e9ccebc694054c9bdc87772d8b5aab58e0bb..9e8b6ee34df23d70887d4fa69a865ebc05f32596 100755 --- a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py +++ b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py @@ -216,7 +216,7 @@ class TaskTemplateTestCase(unittest.TestCase): test_data = test_data_creator.TaskTemplate() r_dict = POST_and_assert_expected_response(self, BASE_URL + '/task_template/', test_data, 201, test_data) url = r_dict['url'] - GET_OK_and_assert_equal_expected_response(self, url + '?format=json', test_data) + GET_OK_and_assert_equal_expected_response(self, url, test_data) def test_task_template_PUT_invalid_raises_error(self): test_data = test_data_creator.TaskTemplate() @@ -269,6 +269,27 @@ class TaskTemplateTestCase(unittest.TestCase): GET_OK_and_assert_equal_expected_response(self, BASE_URL + '/task_template/' + str(id1), test_data_1) GET_OK_and_assert_equal_expected_response(self, BASE_URL + '/task_template/' + str(id2), test_data_2) + def test_task_template_PROTECT_behavior_on_type_choice_deleted(self): + st_test_data = test_data_creator.TaskTemplate() + + # create dependency that is safe to delete (enums are not populated / re-established between tests) + type_data = {'value': 'kickme'} + POST_and_assert_expected_response(self, BASE_URL + '/task_type/', type_data, 201, type_data) + type_url = BASE_URL + '/task_type/kickme/' + + # POST new item and verify + test_data = dict(st_test_data) + test_data['type'] = type_url + url = POST_and_assert_expected_response(self, BASE_URL + '/task_template/', 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(type_url, auth=AUTH) + self.assertEqual(500, response.status_code) + self.assertTrue("ProtectedError" in str(response.content)) + GET_OK_and_assert_equal_expected_response(self, type_url, type_data) + class TaskRelationSelectionTemplateTestCase(unittest.TestCase): def test_task_relation_selection_template_list_apiformat(self): diff --git a/SAS/TMSS/test/tmss_test_data_django_models.py b/SAS/TMSS/test/tmss_test_data_django_models.py index 21ee23b0d2e0330a9a7660f91a2e8e3c72b9f66a..9ad9f6b8432496d871087b752786cbd371121357 100644 --- a/SAS/TMSS/test/tmss_test_data_django_models.py +++ b/SAS/TMSS/test/tmss_test_data_django_models.py @@ -64,12 +64,13 @@ def TaskTemplate_test_data(name="my TaskTemplate", version:str=None) -> dict: if version is None: version = str(uuid.uuid4()) - return {"validation_code_js":"", - "name": name, - "description": 'My TaskTemplate description', - "version": version, - "schema": {"mykey": "my value"}, - "tags": ["TMSS", "TESTING"]} + return {"type": models.TaskType.objects.get(value='observation'), + "validation_code_js":"", + "name": name, + "description": 'My TaskTemplate description', + "version": version, + "schema": {"mykey": "my value"}, + "tags": ["TMSS", "TESTING"]} def TaskRelationSelectionTemplate_test_data(name="my_TaskRelationSelectionTemplate", version:str=None) -> dict: if version is None: diff --git a/SAS/TMSS/test/tmss_test_data_rest.py b/SAS/TMSS/test/tmss_test_data_rest.py index a58cb44b97138e26ba08b4e08c379590dabb089f..765910ff9ab15d675060ac0586ad04d848de4c9f 100644 --- a/SAS/TMSS/test/tmss_test_data_rest.py +++ b/SAS/TMSS/test/tmss_test_data_rest.py @@ -81,15 +81,19 @@ class TMSSRESTTestDataCreator(): "schema": {"mykey": "my value"}, "tags": ["TMSS", "TESTING"]} - def TaskTemplate(self, name="tasktemplate1", version:str=None) -> dict: + def TaskTemplate(self, name="tasktemplate1", task_type_url:str=None, version:str=None) -> dict: if version is None: version = str(uuid.uuid4()) + if task_type_url is None: + task_type_url = self.django_api_url + '/task_type/observation/' + return {"name": name, "description": 'My one observation', "version": version, "schema": {"mykey": "my value"}, "tags": ["TMSS", "TESTING"], + "type": task_type_url, "validation_code_js": "???"} def TaskRelationSelectionTemplate(self, name="taskrelationselectiontemplate1", version:str=None) -> dict: