diff --git a/Docker/lofar-ci/Dockerfile_ci_sas b/Docker/lofar-ci/Dockerfile_ci_sas index e49f816720424fb51a0a7c139c166f2622d881c4..be3799a44d6be5ad0e50ad01de0f9a348a57b942 100644 --- a/Docker/lofar-ci/Dockerfile_ci_sas +++ b/Docker/lofar-ci/Dockerfile_ci_sas @@ -7,7 +7,7 @@ ARG BASE_VERSION=latest FROM ci_base:$BASE_VERSION RUN echo "Installing packages for SAS..." && \ - yum install -y log4cplus log4cplus-devel python3 python3-libs python3-devel boost readline-devel boost-devel binutils-devel boost-python36 boost-python36-devel gettext which openldap-devel npm nodejs git java-11-openjdk python-twisted-core + yum install -y log4cplus log4cplus-devel python3 python3-libs python3-devel boost readline-devel boost-devel binutils-devel boost-python36 boost-python36-devel gettext which openldap-devel git java-11-openjdk python-twisted-core # see https://www.postgresql.org/download/linux/redhat/ on how to install postgresql-server > 9.2 on centos7 RUN yum erase -y postgresql postgresql-server postgresql-devel && \ @@ -18,9 +18,12 @@ ENV PATH /usr/pgsql-9.6/bin:$PATH RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil django djangorestframework djangorestframework-xml ldap==1.0.2 flask fabric coverage python-qpid-proton PyGreSQL numpy h5py psycopg2 testing.postgresql Flask-Testing scipy Markdown django-filter python-ldap python-ldap-test ldap3 djangorestframework django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet -RUN npm install -g npx && \ - npm install -g n && \ - n stable && \ +# Note: nodejs now comes with npm, do not install the npm package separately, since that will be taken from the epel repo and is conflicting. +RUN echo "Installing Nodejs packages..." && \ + curl -sL https://rpm.nodesource.com/setup_14.x | bash - && \ + yum install -y nodejs && \ + npm -v && \ + node -v && \ npm install -g serve USER lofarsys \ No newline at end of file diff --git a/SAS/TMSS/src/remakemigrations.py b/SAS/TMSS/src/remakemigrations.py index 03fdec4cec3f94aa2345d79cb6c91d279b51395c..503432045e7905079d295a0faba956a987eaa66c 100755 --- a/SAS/TMSS/src/remakemigrations.py +++ b/SAS/TMSS/src/remakemigrations.py @@ -76,9 +76,13 @@ class Migration(migrations.Migration): # Start SubTask id with 2 000 000 to avoid overlap with 'old' (test/production) OTDB operations = [ migrations.RunSQL('ALTER SEQUENCE tmssapp_SubTask_id_seq RESTART WITH 2000000;'), migrations.RunPython(populate_choices), + migrations.RunPython(populate_settings), migrations.RunPython(populate_misc), migrations.RunPython(populate_lofar_json_schemas), - migrations.RunPython(populate_settings)] + migrations.RunPython(populate_cycles), + migrations.RunPython(populate_resources), + migrations.RunPython(populate_projects), + migrations.RunPython(populate_test_scheduling_set) ] """ diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py index 6da5be3850dc9afe0aa813a73a1b0843b7a4d612..8a7c77a6b19b7e4cca4f0c808e783a2cb0e83729 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 2.2.12 on 2020-07-07 10:45 +# Generated by Django 3.0.6 on 2020-07-20 14:07 from django.conf import settings import django.contrib.postgres.fields @@ -307,6 +307,15 @@ class Migration(migrations.Migration): ('create_function', models.CharField(help_text='Python function to call to execute the generator.', max_length=128)), ], ), + migrations.CreateModel( + name='PeriodCategory', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), migrations.CreateModel( name='Project', fields=[ @@ -327,27 +336,23 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='ProjectQuota', + name='ProjectCategory', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('value', models.FloatField(help_text='Resource Quota value')), + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), ], + options={ + 'abstract': False, + }, ), migrations.CreateModel( - name='ResourceType', + name='ProjectQuota', fields=[ - ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, default=list, help_text='User-defined search keywords for object.', size=8)), - ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), - ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), - ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), - ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128, primary_key=True, serialize=False)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.FloatField(help_text='Resource Quota value')), ], - options={ - 'abstract': False, - }, ), migrations.CreateModel( - name='ResourceUnit', + name='ResourceType', fields=[ ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, default=list, help_text='User-defined search keywords for object.', size=8)), ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), @@ -983,11 +988,6 @@ class Migration(migrations.Migration): name='project', field=models.ForeignKey(help_text='Project to which this scheduling set belongs.', on_delete=django.db.models.deletion.PROTECT, related_name='scheduling_sets', to='tmssapp.Project'), ), - migrations.AddField( - model_name='resourcetype', - name='resource_unit', - field=models.ForeignKey(help_text='Unit of current resource.', on_delete=django.db.models.deletion.PROTECT, related_name='resource_types', to='tmssapp.ResourceUnit'), - ), migrations.AddField( model_name='projectquota', name='project', @@ -1001,7 +1001,17 @@ class Migration(migrations.Migration): migrations.AddField( model_name='project', name='cycles', - field=models.ManyToManyField(help_text='Cycles to which this project belongs (NULLable).', null=True, related_name='projects', to='tmssapp.Cycle'), + field=models.ManyToManyField(blank=True, help_text='Cycles to which this project belongs (NULLable).', related_name='projects', to='tmssapp.Cycle'), + ), + migrations.AddField( + model_name='project', + name='period_category', + field=models.ForeignKey(help_text='Period category.', null=True, on_delete=django.db.models.deletion.PROTECT, to='tmssapp.PeriodCategory'), + ), + migrations.AddField( + model_name='project', + name='project_category', + field=models.ForeignKey(help_text='Project category.', null=True, on_delete=django.db.models.deletion.PROTECT, to='tmssapp.ProjectCategory'), ), migrations.AddConstraint( model_name='generatortemplate', diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0002_populate.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0002_populate.py index 5f71d8b3e7997447b18548b528557232bc454362..b24c0bccf0ded89c957fa2c6c53c62dea7428c71 100644 --- a/SAS/TMSS/src/tmss/tmssapp/migrations/0002_populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0002_populate.py @@ -21,5 +21,6 @@ class Migration(migrations.Migration): migrations.RunPython(populate_misc), migrations.RunPython(populate_lofar_json_schemas), migrations.RunPython(populate_cycles), + migrations.RunPython(populate_resources), migrations.RunPython(populate_projects), migrations.RunPython(populate_test_scheduling_set) ] diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py index 4335bd06c161cecc4308e5d50c6be1ec57421ebb..a7e72818e4f329a04b263819737f5e61a3751bd3 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py @@ -141,7 +141,8 @@ class SchedulingRelationPlacement(AbstractChoice): AFTER = "after" BEFORE = "before" PARALLEL = "parallel" - + + class Flag(AbstractChoice): """Defines the model and predefined list of possible Flags to be used in Setting. The items in the Choises class below are automagically populated into the database via a data migration.""" @@ -149,6 +150,28 @@ class Flag(AbstractChoice): AUTOSCHEDULE = "allow_scheduling_observations" +class PeriodCategory(AbstractChoice): + """Defines the model and predefined list of possible period categories to be used in Project. + The items in the Choices class below are automagically populated into the database via a data migration.""" + + class Choices(Enum): + SINGLE_CYCLE = "single_cycle" + LONG_TERM = "long_term" + UNBOUNDED = "unbounded" + + +class ProjectCategory(AbstractChoice): + """Defines the model and predefined list of possible project categories to be used in Project. + The items in the Choices class below are automagically populated into the database via a data migration.""" + + class Choices(Enum): + REGULAR = "regular" + USER_SHARED_SUPPORT = "user_shared_support" + COMMISSIONING = "commissioning" + DDT = "ddt" + TEST = "test" + + # concrete models class Setting(BasicCommon): @@ -253,14 +276,39 @@ class Cycle(NamedCommonPK): class Project(NamedCommonPK): - # todo: cycles should be protected since we have to manually decide to clean up projects with a cycle or keep them without cycle, however, ManyToManyField does not allow for that - cycles = ManyToManyField('Cycle', related_name='projects', null=True, help_text='Cycles to which this project belongs (NULLable).') + cycles = ManyToManyField('Cycle', blank=True, related_name='projects', help_text='Cycles to which this project belongs (NULLable).') priority_rank = FloatField(null=False, help_text='Priority of this project w.r.t. other projects. Projects can interrupt observations of lower-priority projects.') # todo: add if needed: validators=[MinValueValidator(0.0), MaxValueValidator(1.0)] trigger_priority = IntegerField(default=1000, help_text='Priority of this project w.r.t. triggers.') # todo: verify meaning and add to help_text: "Triggers with higher priority than this threshold can interrupt observations of projects." can_trigger = BooleanField(default=False, help_text='True if this project is allowed to supply observation requests on the fly, possibly interrupting currently running observations (responsive telescope).') private_data = BooleanField(default=True, help_text='True if data of this project is sensitive. Sensitive data is not made public.') expert = BooleanField(default=False, help_text='Expert projects put more responsibility on the PI.') filler = BooleanField(default=False, help_text='Use this project to fill up idle telescope time.') + project_category = ForeignKey('ProjectCategory', null=True, on_delete=PROTECT, help_text='Project category.') + period_category = ForeignKey('PeriodCategory', null=True, on_delete=PROTECT, help_text='Period category.') + + # JK, 29/07/20 - after discussion with Sander, it turns out that the ticket TMSS-277 was a misunderstanding. + # 'default' does not refer to 'default values' that are supposed to be filled in by the backend. + # It was meant to be 'resource_types displayed in the frontend by default', where the other resource_types are + # optionally added to the set of quota. These can then be customized in the frontend and are created by the + # frontend in the backend, but no quota are intended to be added automatically. So nothing is really needed in + # the backend for this (apart from the set of predefined resource_types). + # There was some open question on whether there may be a required subset of quota that have to be enforced. So + # I'll leave this in for now, until that question is cleared up. + # + # # also create default project quotas when projects are created + # def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + # creating = self._state.adding # True on create, False on update + # super().save(force_insert, force_update, using, update_fields) + # if creating: + # # todo: review these defaults for being sensible + # ProjectQuota.objects.create(resource_type=ResourceType.objects.get(name="lta_storage"), value=1024^4, project=self) + # ProjectQuota.objects.create(resource_type=ResourceType.objects.get(name="cep_storage"), value=1024^4, project=self) + # ProjectQuota.objects.create(resource_type=ResourceType.objects.get(name="cep_processing_time"), value=60*60*24, project=self) + # ProjectQuota.objects.create(resource_type=ResourceType.objects.get(name="lofar_observing_time"), value=60*60*24, project=self) + # ProjectQuota.objects.create(resource_type=ResourceType.objects.get(name="lofar_observing_time_prio_a"), value=60*60*12, project=self) + # ProjectQuota.objects.create(resource_type=ResourceType.objects.get(name="lofar_observing_time_prio_b"), value=60*60*12, project=self) + # ProjectQuota.objects.create(resource_type=ResourceType.objects.get(name="support_time"), value=60*60*6, project=self) + # ProjectQuota.objects.create(resource_type=ResourceType.objects.get(name="number_of_triggers"), value=42, project=self) class ProjectQuota(Model): @@ -270,12 +318,9 @@ class ProjectQuota(Model): class ResourceType(NamedCommonPK): - resource_unit = ForeignKey('ResourceUnit', related_name="resource_types", on_delete=PROTECT, help_text='Unit of current resource.') - - -class ResourceUnit(NamedCommonPK): pass + class SchedulingSet(NamedCommon): generator_doc = JSONField(null=True, help_text='Parameters for the generator (NULLable).') generator_template = ForeignKey('GeneratorTemplate', on_delete=SET_NULL, null=True, help_text='Generator for the scheduling units in this set (NULLable).') diff --git a/SAS/TMSS/src/tmss/tmssapp/populate.py b/SAS/TMSS/src/tmss/tmssapp/populate.py index 35ca8990516e6c2470f0f87791064dd2b2f435d8..c8ea9780f7d44872d2ab7d3859bfa6e8568cb4f6 100644 --- a/SAS/TMSS/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/populate.py @@ -36,7 +36,8 @@ def populate_choices(apps, schema_editor): :return: None ''' for choice_class in [Role, Datatype, Dataformat, CopyReason, - SubtaskState, SubtaskType, StationType, Algorithm, ScheduleMethod, SchedulingRelationPlacement, Flag]: + SubtaskState, SubtaskType, StationType, Algorithm, ScheduleMethod, SchedulingRelationPlacement, + Flag, ProjectCategory, PeriodCategory]: choice_class.objects.bulk_create([choice_class(value=x.value) for x in choice_class.Choices]) def populate_settings(apps, schema_editor): @@ -206,12 +207,14 @@ def populate_projects(apps, schema_editor): def populate_resources(apps, schema_editor): - ru_bytes = ResourceUnit.objects.create(name="bytes", description="Bytes") - ru_hours = ResourceUnit.objects.create(name="hours", description="duration in hours") - - ResourceType.objects.create(name="lta_storage", description="Amount of storage in LTA", resource_unit=ru_bytes) - ResourceType.objects.create(name="cep_storage", description="Amount of storage at CEP processing cluster", resource_unit=ru_bytes) - ResourceType.objects.create(name="cep_processing_hours", description="Number of processing hours for CEP processing cluster", resource_unit=ru_hours) + ResourceType.objects.create(name="lta_storage", description="Amount of storage in the LTA (in bytes)") + ResourceType.objects.create(name="cep_storage", description="Amount of storage on the CEP processing cluster (in bytes)") + ResourceType.objects.create(name="cep_processing_time", description="Processing time on the CEP processing cluster (in seconds)") + ResourceType.objects.create(name="lofar_observing_time", description="Observing time (in seconds)") + ResourceType.objects.create(name="lofar_observing_time_prio_a", description="Observing time with priority A (in seconds)") + ResourceType.objects.create(name="lofar_observing_time_prio_b", description="Observing time with priority B (in seconds)") + ResourceType.objects.create(name="support_time", description="Support time by human (in seconds)") + ResourceType.objects.create(name="number_of_triggers", description="Number of trigger events (as integer)") def populate_misc(apps, schema_editor): diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py index 90152f462b3a860e144bb87a210f1c3a3c01bbdc..89dc4d9016dde72ba30fec93f94b7f408b4f643d 100644 --- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py @@ -186,13 +186,6 @@ class ProjectQuotaSerializer(RelationalHyperlinkedModelSerializer): extra_fields = ['resource_type'] -class ResourceUnitSerializer(RelationalHyperlinkedModelSerializer): - class Meta: - model = models.ResourceUnit - fields = '__all__' - extra_fields = ['name'] - - class ResourceTypeSerializer(RelationalHyperlinkedModelSerializer): class Meta: model = models.ResourceType @@ -212,6 +205,18 @@ class SettingSerializer(serializers.ModelSerializer): fields = '__all__' +class ProjectCategorySerializer(serializers.ModelSerializer): + class Meta: + model = models.ProjectCategory + fields = '__all__' + + +class PeriodCategorySerializer(serializers.ModelSerializer): + class Meta: + model = models.PeriodCategory + fields = '__all__' + + class SchedulingSetSerializer(RelationalHyperlinkedModelSerializer): # Create a JSON editor form to replace the simple text field based on the schema in the template that this diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py index 93ab6971734d1c740e52c75b4c6e75407a5b7dd0..ffc8dcf51f465a2edef08732b3ccef9085e91d5a 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py @@ -147,11 +147,6 @@ class CopyReasonViewSet(LOFARViewSet): queryset = models.CopyReason.objects.all() serializer_class = serializers.CopyReasonSerializer - -class ResourceUnitViewSet(LOFARViewSet): - queryset = models.ResourceUnit.objects.all() - serializer_class = serializers.ResourceUnitSerializer - class TaskConnectorTypeViewSet(LOFARViewSet): queryset = models.TaskConnectorType.objects.all() @@ -186,6 +181,16 @@ class ProjectNestedViewSet(LOFARNestedViewSet): class ProjectQuotaViewSet(LOFARViewSet): queryset = models.ProjectQuota.objects.all() serializer_class = serializers.ProjectQuotaSerializer + + def get_queryset(self): + queryset = models.ProjectQuota.objects.all() + + # query by project + project = self.request.query_params.get('project', None) + if project is not None: + return queryset.filter(project=project) + + return queryset class ResourceTypeViewSet(LOFARViewSet): @@ -208,6 +213,16 @@ class SettingViewSet(LOFARViewSet): serializer_class = serializers.SettingSerializer +class PeriodCategoryViewSet(LOFARViewSet): + queryset = models.PeriodCategory.objects.all() + serializer_class = serializers.PeriodCategorySerializer + + +class ProjectCategoryViewSet(LOFARViewSet): + queryset = models.ProjectCategory.objects.all() + serializer_class = serializers.ProjectCategorySerializer + + class SchedulingUnitDraftViewSet(LOFARViewSet): queryset = models.SchedulingUnitDraft.objects.all() serializer_class = serializers.SchedulingUnitDraftSerializer diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py index 6dda70922b4b63118f7deed3c8e9c9a08b3dea9a..e49755be62a54b36af280073a128b72db1ae8ad3 100644 --- a/SAS/TMSS/src/tmss/urls.py +++ b/SAS/TMSS/src/tmss/urls.py @@ -87,6 +87,8 @@ router.register(r'datatype', viewsets.DatatypeViewSet) router.register(r'dataformat', viewsets.DataformatViewSet) router.register(r'copy_reason', viewsets.CopyReasonViewSet) router.register(r'flag', viewsets.FlagViewSet) +router.register(r'period_category', viewsets.PeriodCategoryViewSet) +router.register(r'project_category', viewsets.ProjectCategoryViewSet) # templates router.register(r'generator_template', viewsets.GeneratorTemplateViewSet) @@ -102,7 +104,6 @@ router.register(r'default_task_relation_selection_template', viewsets.DefaultTas # instances router.register(r'cycle', viewsets.CycleViewSet) router.register(r'project', viewsets.ProjectViewSet) -router.register(r'resource_unit', viewsets.ResourceUnitViewSet) router.register(r'resource_type', viewsets.ResourceTypeViewSet) router.register(r'project_quota', viewsets.ProjectQuotaViewSet) router.register(r'setting', viewsets.SettingViewSet) diff --git a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py index b55b2d4669438ae85fcca09021075d3d0c2e1dcf..d192f6486088fdf8d2860a7d90731150715b3b05 100755 --- a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py +++ b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py @@ -720,9 +720,11 @@ class ProjectTestCase(unittest.TestCase): project_test_data = test_data_creator.Project() # POST and GET a new item and assert correctness - r_dict = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, project_test_data) + expected = project_test_data.copy() + expected.pop('project_quota') # exclude project_quota from comparison, because these get auto-generated + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, expected) url = r_dict['url'] - GET_OK_and_assert_equal_expected_response(self, url, project_test_data) + GET_OK_and_assert_equal_expected_response(self, url, expected) def test_project_PUT_invalid_raises_error(self): PUT_and_assert_expected_response(self, BASE_URL + '/project/9876789876/', test_data_creator.Project(), 404, {}) @@ -731,40 +733,52 @@ class ProjectTestCase(unittest.TestCase): project_test_data = test_data_creator.Project() # POST new item, verify - r_dict = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, project_test_data) + expected = project_test_data.copy() + expected.pop('project_quota') # exclude project_quota from comparison, because these get auto-generated + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, expected) url = r_dict['url'] - GET_OK_and_assert_equal_expected_response(self, url, project_test_data) + GET_OK_and_assert_equal_expected_response(self, url, expected) # PUT new values, verify test_data = dict(test_data_creator.Project("other description")) test_data['name'] = project_test_data['name'] # since name is PK, need to keep that unchanged - PUT_and_assert_expected_response(self, url, test_data, 200, test_data) - GET_OK_and_assert_equal_expected_response(self, url, test_data) + expected = test_data.copy() + expected.pop('project_quota') # exclude project_quota from comparison, because these get auto-generated + PUT_and_assert_expected_response(self, url, test_data, 200, expected) + GET_OK_and_assert_equal_expected_response(self, url, expected) def test_project_PATCH(self): project_test_data = test_data_creator.Project() # POST new item, verify - r_dict = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, project_test_data) + expected = project_test_data.copy() + expected.pop('project_quota') # exclude project_quota from comparison, because these get auto-generated + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, expected) url = r_dict['url'] - GET_OK_and_assert_equal_expected_response(self, url, project_test_data) + GET_OK_and_assert_equal_expected_response(self, url, expected) test_patch = {"priority_rank": 1.0, "tags": ["SUPERIMPORTANT"]} # PATCH item and verify PATCH_and_assert_expected_response(self, url, test_patch, 200, test_patch) - expected_data = dict(project_test_data) - expected_data.update(test_patch) - GET_OK_and_assert_equal_expected_response(self, url, expected_data) + expected.update(test_patch) + GET_OK_and_assert_equal_expected_response(self, url, expected) def test_project_DELETE(self): project_test_data = test_data_creator.Project() # POST new item, verify - r_dict = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, project_test_data) + expected = project_test_data.copy() + expected.pop('project_quota') # exclude project_quota from comparison, because these get auto-generated + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, expected) url = r_dict['url'] - GET_OK_and_assert_equal_expected_response(self, url, project_test_data) + GET_OK_and_assert_equal_expected_response(self, url, expected) + + # DELETE related auto-generated project_quota first + project_quotas = r_dict['project_quota'] + for project_quota in project_quotas: + DELETE_and_assert_gone(self, project_quota) # DELETE and check it's gone DELETE_and_assert_gone(self, url) @@ -776,13 +790,15 @@ class ProjectTestCase(unittest.TestCase): cycle_url = POST_and_assert_expected_response(self, BASE_URL + '/cycle/', cycle_test_data, 201, cycle_test_data)['url'] test_data = dict(test_data_creator.Project()) test_data['cycles'] = [cycle_url] - url = POST_and_assert_expected_response(self, BASE_URL + '/project/', test_data, 201, test_data)['url'] + expected = test_data.copy() + expected.pop('project_quota') # exclude project_quota from comparison, because these get auto-generated + url = POST_and_assert_expected_response(self, BASE_URL + '/project/', test_data, 201, expected)['url'] # verify - GET_OK_and_assert_equal_expected_response(self, url, test_data) + GET_OK_and_assert_equal_expected_response(self, url, expected) # add project reference to cycle test data (we make Django add that to the cycle in serializer) - cycle_test_data['projects'] = [url] # add the + cycle_test_data['projects'] = [url] # 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... @@ -907,7 +923,9 @@ class ProjectQuotaTestCase(unittest.TestCase): # POST new item with dependencies project_test_data = test_data_creator.Project() - project_url = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, project_test_data)['url'] + expected = project_test_data.copy() + expected.pop('project_quota') # exclude project_quota from comparison, because these get auto-generated + project_url = POST_and_assert_expected_response(self, BASE_URL + '/project/', project_test_data, 201, expected)['url'] project_quota_test_data = dict(test_data_creator.ProjectQuota(project_url=project_url)) project_quota_url = POST_and_assert_expected_response(self, BASE_URL + '/project_quota/', project_quota_test_data, 201, project_quota_test_data)['url'] @@ -1595,7 +1613,7 @@ class TaskRelationDraftTestCase(unittest.TestCase): # assert the returned list contains related items, A list of length 1 is retrieved GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/task_draft/%s/task_relation_draft/' % task_draft_1.id, test_data_1, 1) # assert an existing related producer is returned - + class SchedulingUnitBlueprintTestCase(unittest.TestCase): @classmethod @@ -2335,7 +2353,7 @@ class TaskSchedulingRelationBlueprintTestCase(unittest.TestCase): # verify GET_OK_and_assert_equal_expected_response(self, url, tsrb_test_data) - #Get the URL of first task blueprint + #Get the URL of first task blueprint test_data = dict(tsrb_test_data) task_blueprint_url=test_data['first'] diff --git a/SAS/TMSS/test/tmss_test_data_django_models.py b/SAS/TMSS/test/tmss_test_data_django_models.py index 2d8a16334f71af6d49759d28f67f6540991b053c..e9db5ec6fd350385f65e60cef32c5f3e91c18670 100644 --- a/SAS/TMSS/test/tmss_test_data_django_models.py +++ b/SAS/TMSS/test/tmss_test_data_django_models.py @@ -111,18 +111,10 @@ def Project_test_data() -> dict: "expert": True, "filler": False} -def ResourceUnit_test_data() -> dict: - return { - "tags": [], - "description": 'my description ' + str(uuid.uuid4()), - "name": 'my_resource_unit_' + str(uuid.uuid4()), - } - def ResourceType_test_data() -> dict: return { "tags": [], "description": 'my description ' + str(uuid.uuid4()), - "resource_unit": models.ResourceUnit.objects.create(**ResourceUnit_test_data()), "name": 'my_resource_type_' + str(uuid.uuid4()), } diff --git a/SAS/TMSS/test/tmss_test_data_rest.py b/SAS/TMSS/test/tmss_test_data_rest.py index ada401713f68345bfd3bbf9374f62c6ecc7e2a84..a75637c6c484a5a4e589c3d5a2e81f25755e0dfa 100644 --- a/SAS/TMSS/test/tmss_test_data_rest.py +++ b/SAS/TMSS/test/tmss_test_data_rest.py @@ -141,22 +141,13 @@ class TMSSRESTTestDataCreator(): "priority_rank": 1.0, "trigger_priority": 1000, "can_trigger": False, - "private_data": True} + "private_data": True, + "cycles": []} - def ResourceUnit(self): - return { - "tags": [], - "description": 'my description ' + str(uuid.uuid4()), - "name": 'my_resource_unit_' + str(uuid.uuid4()) - } - - def ResourceType(self, description="my resource_type description", resource_url=None): - if resource_url is None: - resource_url = self.post_data_and_get_url(self.ResourceUnit(), '/resource_unit/') + def ResourceType(self, description="my resource_type description"): return { "tags": [], "description": description, - "resource_unit": resource_url, "name": 'my_resource_type_' + str(uuid.uuid4()) }