diff --git a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py index 95118c912f9382dc2d9fd032b7d70211b5d5ec8f..f11e52baff63d43af941a5c468149974534747b9 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py @@ -181,6 +181,12 @@ class Subtask(BasicCommon): class SubtaskStateLog(BasicCommon): """ History of state changes on subtasks + This is now a very specific solution and based on what SOS communicated is what they are regularly interested in. + Maybe one or two additional log tables for other models are benefitial and should be added at some point. + + Note that we could of course also log on the db level and there is also a variety of audit middlewares for Django + available to keep track of changes more generally: https://djangopackages.org/grids/g/model-audit/ + This seems a bit overkill at the moment and we have to manage access to those logs etc., this needs tbd. """ user = ForeignKey(User, null=False, editable=False, on_delete=PROTECT, help_text='The user who changed the state of the subtask.') user_identifier = CharField(null=False, editable=False, max_length=128, help_text='The ID of the user who changed the state of the subtask.') diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/serializers/scheduling.py index 202f7f716fc4fa148df2bfd70837c1f5b0239f87..3967e8e168e5c28395f8db2b592bf001deddaa88 100644 --- a/SAS/TMSS/src/tmss/tmssapp/serializers/scheduling.py +++ b/SAS/TMSS/src/tmss/tmssapp/serializers/scheduling.py @@ -172,16 +172,19 @@ class SubtaskSerializerJSONeditorOnline(RelationalHyperlinkedModelSerializer): # todo: Shall we use one of the default templates for the init? logger.warning('Could not determine schema, hence no fancy JSON form. Expected for list view.') + # Intercept updates to also create a log entry def update(self, instance, validated_data): - user = self.context.get('request').user - log_entry = models.SubtaskStateLog(user=user, - user_identifier=user.email, - subtask=instance, - old_state=instance.state, - new_state=validated_data.get('state')) - log_entry.save() - super().update(instance, validated_data) - return instance + if instance.state is not None \ + and validated_data.get('state') is not None \ + and instance.state != validated_data.get('state'): + user = self.context.get('request').user + log_entry = models.SubtaskStateLog(user=user, + user_identifier=user.email, + subtask=instance, + old_state=instance.state, + new_state=validated_data.get('state')) + log_entry.save() + return super().update(instance, validated_data) class Meta: model = models.Subtask diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py index 2277d1876454c13abd08b36e307d2591d4b657f7..8eb8e847c4e599521e5cb63cf2947ea26a05e9f9 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py @@ -43,6 +43,17 @@ class SubtaskStateViewSet(LOFARViewSet): class SubtaskStateLogViewSet(LOFARViewSet): + + def get_queryset(self): + queryset = models.SubtaskStateLog.objects.all() + + # query by subtask + subtask = self.request.query_params.get('subtask', None) + if subtask is not None: + return queryset.filter(subtask=subtask) + + return queryset + queryset = models.SubtaskStateLog.objects.all() serializer_class = serializers.SubtaskStateLogSerializer diff --git a/SAS/TMSS/test/t_tmssapp_scheduling_functional.py b/SAS/TMSS/test/t_tmssapp_scheduling_functional.py index 1c46c09ee95846377399d1b63c8f1c45e11e4bc1..480cdf15bc16690fd295667a8b259b096ceef9ae 100755 --- a/SAS/TMSS/test/t_tmssapp_scheduling_functional.py +++ b/SAS/TMSS/test/t_tmssapp_scheduling_functional.py @@ -378,6 +378,31 @@ class SubtaskTestCase(unittest.TestCase): self.assertTrue("ProtectedError" in str(response.content)) GET_and_assert_expected_response(self, specifications_template_url, 200, stt_test_data) + def test_subtask_state_log_records(self): + st_test_data = test_data_creator.Subtask() + + # POST new item, verify + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/subtask/', st_test_data, 201, st_test_data) + url = r_dict['url'] + GET_and_assert_expected_response(self, url, 200, st_test_data) + + # Verify state log count is zero + segments = url.split('/') + identifier = '' + while identifier == '': + identifier = segments.pop() + GET_and_assert_expected_response(self, BASE_URL + '/subtask_state_log/?subtask=' + identifier, 200, {"count":0}) + + # PATCH item with something else than state and verify no log record is created + test_patch = {"specifications_doc": {"somespec": "somevalue"}} + PATCH_and_assert_expected_response(self, url, test_patch, 200, test_patch) + GET_and_assert_expected_response(self, BASE_URL + '/subtask_state_log/?subtask=' + identifier, 200, {"count": 0}) + + # PATCH item with state update and verify log record is created + test_patch = {"state": BASE_URL + "/subtask_state/finishing/"} + PATCH_and_assert_expected_response(self, url, test_patch, 200, test_patch) + GET_and_assert_expected_response(self, BASE_URL + '/subtask_state_log/?subtask=' + identifier, 200, {"count": 1}) + class DataproductTestCase(unittest.TestCase): @classmethod