Commit 804f8ab0 authored by Joern jkuensem's avatar Joern jkuensem

TMSS-162: Add user reference to subtask and log on save to also log creation...

TMSS-162: Add user reference to subtask and log on save to also log creation and updates through ORM
parent ea584613
......@@ -20,7 +20,11 @@ class Migration(migrations.Migration):
('tmssapp', '%s'),
]
operations = [ migrations.RunPython(populate_choices) ]
# 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_misc),
migrations.RunPython(populate_lofar_json_schemas) ]
"""
......@@ -42,7 +46,7 @@ def delete_old_migrations():
for f in [path for path in files if ("auto" in path or "populate" in path)]:
logger.info('Deleting: %s' % f)
os.remove(f)
execute_and_log('git rm %s' % f)
#execute_and_log('git rm %s' % f)
def make_django_migrations():
......@@ -88,7 +92,7 @@ def remake_migrations():
delete_old_migrations()
make_django_migrations()
make_populate_migration()
put_migrations_under_version_control()
#put_migrations_under_version_control()
if __name__ == "__main__":
......
# Generated by Django 2.2.5 on 2020-02-17 13:55
# Generated by Django 3.0.3 on 2020-03-24 18:30
from django.conf import settings
import django.contrib.postgres.fields
import django.contrib.postgres.fields.jsonb
import django.contrib.postgres.indexes
......@@ -11,7 +12,9 @@ class Migration(migrations.Migration):
initial = True
dependencies = []
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
......@@ -280,6 +283,7 @@ class Migration(migrations.Migration):
('priority', models.IntegerField(help_text='Absolute priority of this subtask (higher value means more important).')),
('scheduler_input_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Partial specifications, as input for the scheduler.')),
('cluster', models.ForeignKey(help_text='Where the Subtask is scheduled to run (NULLable).', null=True, on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Cluster')),
('created_or_updated_by_user', models.ForeignKey(editable=False, help_text='The user who created / updated the subtask.', null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('schedule_method', models.ForeignKey(help_text='Which method to use for scheduling this Subtask. One of (MANUAL, BATCH, DYNAMIC).', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.ScheduleMethod')),
],
options={
......@@ -516,6 +520,23 @@ class Migration(migrations.Migration):
'abstract': False,
},
),
migrations.CreateModel(
name='SubtaskStateLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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.')),
('user_identifier', models.CharField(editable=False, help_text='The ID of the user who changed the state of the subtask.', max_length=128)),
('new_state', models.ForeignKey(editable=False, help_text='Subtask state after update (see Subtask State Machine).', on_delete=django.db.models.deletion.PROTECT, related_name='is_new_state_of', to='tmssapp.SubtaskState')),
('old_state', models.ForeignKey(editable=False, help_text='Subtask state before update (see Subtask State Machine).', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='is_old_state_of', to='tmssapp.SubtaskState')),
('subtask', models.ForeignKey(editable=False, help_text='Subtask to which this state change refers.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.Subtask')),
('user', models.ForeignKey(editable=False, help_text='The user who changed the state of the subtask.', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='SubtaskOutput',
fields=[
......@@ -804,6 +825,10 @@ class Migration(migrations.Migration):
model_name='taskconnectors',
index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_0ebd6d_gin'),
),
migrations.AddIndex(
model_name='subtaskstatelog',
index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_70e4a2_gin'),
),
migrations.AddIndex(
model_name='subtaskoutput',
index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_e25b4c_gin'),
......
......@@ -14,3 +14,4 @@ class Migration(migrations.Migration):
migrations.RunPython(populate_choices),
migrations.RunPython(populate_misc),
migrations.RunPython(populate_lofar_json_schemas) ]
......@@ -154,6 +154,11 @@ class Subtask(BasicCommon):
cluster = ForeignKey('Cluster', null=True, on_delete=PROTECT, help_text='Where the Subtask is scheduled to run (NULLable).')
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?
created_or_updated_by_user = ForeignKey(User, null=True, editable=False, on_delete=PROTECT, help_text='The user who created / updated the subtask.')
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:
......@@ -175,7 +180,14 @@ 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()
creating = self._state.adding
super().save(force_insert, force_update, using, update_fields)
# log if either state update or new entry:
if self.state != self.__original_state or creating is True:
state_update = SubtaskStateLog(subtask=self, old_state=self.__original_state, new_state=self.state,
user=self.created_or_updated_by_user, user_identifier=self.created_or_updated_by_user.email)
state_update.save()
class SubtaskStateLog(BasicCommon):
......@@ -191,7 +203,7 @@ class SubtaskStateLog(BasicCommon):
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).')
old_state = ForeignKey('SubtaskState', null=True, 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).')
......
......@@ -149,7 +149,6 @@ class DataproductHashSerializer(serializers.HyperlinkedModelSerializer):
class SubtaskSerializerJSONeditorOnline(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.
#
......@@ -171,20 +170,30 @@ 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 create(self, validated_data):
validated_data['created_or_updated_by_user'] = self.context.get('request').user
return models.Subtask.objects.create(**validated_data)
def update(self, instance, validated_data):
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()
validated_data['created_or_updated_by_user'] = self.context.get('request').user
return super().update(instance, validated_data)
# todo: remove the following if we are happy to log on the model level
# Intercept updates to also create a log entry
# def update(self, instance, validated_data):
# 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
fields = '__all__'
#fields = '__all__'
exclude = ('created_or_updated_by_user',)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment