From 0dd23ea384aa490564dbf961c06ddffc2b54d5a8 Mon Sep 17 00:00:00 2001
From: jkuensem <jkuensem@physik.uni-bielefeld.de>
Date: Tue, 17 Mar 2020 10:44:50 +0100
Subject: [PATCH] TMSS-162: Add user references to log table for subtask state
 changes

---
 SAS/TMSS/src/tmss/tmssapp/models/scheduling.py    | 12 +++++-------
 .../src/tmss/tmssapp/serializers/scheduling.py    | 15 ++++++++++-----
 .../src/tmss/tmssapp/serializers/specification.py |  9 +++++++++
 .../src/tmss/tmssapp/viewsets/specification.py    | 10 ++++++++++
 SAS/TMSS/src/tmss/urls.py                         |  2 +-
 5 files changed, 35 insertions(+), 13 deletions(-)

diff --git a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
index 2260dad7658..95118c912f9 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
@@ -5,6 +5,7 @@ This file contains the database models
 from django.db.models import ForeignKey, CharField, DateTimeField, BooleanField, IntegerField, BigIntegerField, \
     ManyToManyField, CASCADE, SET_NULL, PROTECT
 from django.contrib.postgres.fields import ArrayField, JSONField
+from django.contrib.auth.models import User
 from .specification import AbstractChoice, BasicCommon, Template, NamedCommon # , <TaskBlueprint
 from enum import Enum
 from rest_framework.serializers import HyperlinkedRelatedField
@@ -95,6 +96,7 @@ class ScheduleMethod(AbstractChoice):
         BATCH = 'batch'
         DYNAMIC = 'dynamic'
 
+
 #
 # Templates
 #
@@ -153,10 +155,6 @@ class Subtask(BasicCommon):
     scheduler_input_doc = JSONField(help_text='Partial specifications, as input for the scheduler.')
     # resource_claim = ForeignKey("ResourceClaim", null=False, on_delete=PROTECT) # todo <-- how is this external reference supposed to work?
 
-    def __init__(self, *args, **kwargs):
-        super(Subtask, self).__init__(*args, **kwargs)
-        self.__original_state = self.state
-
     def validate_specification_against_schema(self):
         if self.specifications_doc is None or self.specifications_template_id is None:
             return
@@ -177,9 +175,6 @@ class Subtask(BasicCommon):
         '''override of normal save method, doing a validation of the specification against the schema first
         :raises SpecificationException in case the specification does not validate against the schema'''
         self.validate_specification_against_schema()
-        if self.state != self.__original_state:
-            state_update = SubtaskStateLog(subtask=self, old_state=self.__original_state, new_state=self.state)
-            state_update.save()
         super().save(force_insert, force_update, using, update_fields)
 
 
@@ -187,6 +182,8 @@ class SubtaskStateLog(BasicCommon):
     """
     History of state changes on subtasks
     """
+    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.')
     subtask = ForeignKey('Subtask', null=False,  editable=False, on_delete=CASCADE, help_text='Subtask to which this state change refers.')
     old_state = ForeignKey('SubtaskState', null=False,  editable=False, on_delete=PROTECT, related_name='is_old_state_of', help_text='Subtask state before update (see Subtask State Machine).')
     new_state = ForeignKey('SubtaskState', null=False, editable=False, on_delete=PROTECT, related_name='is_new_state_of', help_text='Subtask state after update (see Subtask State Machine).')
@@ -265,3 +262,4 @@ class DataproductHash(BasicCommon):
     dataproduct = ForeignKey('Dataproduct', on_delete=PROTECT, help_text='The dataproduct to which this hash refers.')
     algorithm = ForeignKey('Algorithm', null=False, on_delete=PROTECT, help_text='Algorithm used (MD5, AES256).')
     hash = CharField(max_length=128, help_text='Hash value.')
+
diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/serializers/scheduling.py
index 2f9b6675123..6a0f6ecebcf 100644
--- a/SAS/TMSS/src/tmss/tmssapp/serializers/scheduling.py
+++ b/SAS/TMSS/src/tmss/tmssapp/serializers/scheduling.py
@@ -171,11 +171,16 @@ 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.')
 
-    #def create(self, validated_data):
-        #subtaskstateupdate_serializer = SubtaskStateLogSerializer(validated_data.get('customer'))
-        #subtaskstateupdate_serializer.save()
-    #    return User.objects.create(**validated_data)
-    #   = self.context.get('request').user
+    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
 
     class Meta:
         model = models.Subtask
diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
index e3804396d7e..f71570d16e7 100644
--- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
@@ -4,6 +4,7 @@ This file contains the serializers (for the elsewhere defined data models)
 
 from rest_framework import serializers
 from .. import models
+from django.contrib.auth.models import User
 from rest_framework import decorators
 
 class RelationalHyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
@@ -17,6 +18,14 @@ class RelationalHyperlinkedModelSerializer(serializers.HyperlinkedModelSerialize
             return expanded_fields
 
 
+# This is required for keeping a user reference as ForeignKey in other models
+# (I think so that the HyperlinkedModelSerializer can generate a URI)
+class UserSerializer(serializers.Serializer):
+    class Meta:
+        model = User
+        fields = '__all__'
+
+
 class TagsSerializer(serializers.HyperlinkedModelSerializer):
     class Meta:
         model = models.Tags
diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py
index b9451879335..2d7b11a7fcf 100644
--- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py
@@ -4,6 +4,8 @@ This file contains the viewsets (based on the elsewhere defined data models and
 
 from django.shortcuts import get_object_or_404
 from django.http import JsonResponse
+from django.contrib.auth.models import User
+from rest_framework.viewsets import ReadOnlyModelViewSet
 
 from rest_framework.decorators import permission_classes
 from rest_framework.permissions import IsAuthenticatedOrReadOnly, DjangoModelPermissions
@@ -17,6 +19,14 @@ from lofar.sas.tmss.tmss.tmssapp import serializers
 
 from lofar.common.json_utils import get_default_json_object_for_schema
 
+
+# This is required for keeping a user reference as ForeignKey in other models
+# (I think so that the HyperlinkedModelSerializer can generate a URI)
+class UserViewSet(ReadOnlyModelViewSet):
+    queryset = User.objects.all()
+    serializer_class = serializers.UserSerializer
+
+
 class TagsViewSet(LOFARViewSet):
     queryset = models.Tags.objects.all()
     serializer_class = serializers.TagsSerializer
diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py
index 4a97752637e..1f0c91950f4 100644
--- a/SAS/TMSS/src/tmss/urls.py
+++ b/SAS/TMSS/src/tmss/urls.py
@@ -135,7 +135,7 @@ router.register(r'dataproduct_archive_info', viewsets.DataproductArchiveInfoView
 router.register(r'dataproduct_hash', viewsets.DataproductHashViewSet)
 router.register(r'task_relation_blueprint', viewsets.TaskRelationBlueprintViewSet)
 router.register(r'subtask_state_log', viewsets.SubtaskStateLogViewSet)
-
+router.register(r'user', viewsets.UserViewSet)
 
 # ---
 
-- 
GitLab