From ab93d0feaafb3297d7faf1dc9eb48c70aa07f900 Mon Sep 17 00:00:00 2001
From: Nico Vermaas <vermaas@astron.nl>
Date: Fri, 15 Jan 2021 17:01:22 +0100
Subject: [PATCH] complete overhaul of the datamodel

---
 atdb/atdb/settings/dev.py                     |   6 +-
 atdb/taskdatabase/admin.py                    |   6 +-
 atdb/taskdatabase/config.py                   |   4 +-
 atdb/taskdatabase/migrations/0001_initial.py  |  67 ----
 .../migrations/0002_taskobject_metadata.py    |  18 --
 .../migrations/0003_auto_20210114_1133.py     |  26 --
 atdb/taskdatabase/migrations/__init__.py      |   0
 atdb/taskdatabase/models.py                   |  81 ++---
 atdb/taskdatabase/serializers.py              |  44 +--
 atdb/taskdatabase/services/algorithms.py      |  73 +----
 atdb/taskdatabase/services/signals.py         |  28 +-
 .../templates/taskdatabase/index.html         |  17 +-
 .../templates/taskdatabase/observations.html  | 100 ------
 .../templates/taskdatabase/pagination.html    |   2 +-
 .../templates/taskdatabase/tasks.html         |  86 +++++
 atdb/taskdatabase/urls.py                     |  41 +--
 atdb/taskdatabase/views.py                    | 295 ++++++------------
 17 files changed, 287 insertions(+), 607 deletions(-)
 delete mode 100644 atdb/taskdatabase/migrations/0001_initial.py
 delete mode 100644 atdb/taskdatabase/migrations/0002_taskobject_metadata.py
 delete mode 100644 atdb/taskdatabase/migrations/0003_auto_20210114_1133.py
 delete mode 100644 atdb/taskdatabase/migrations/__init__.py
 delete mode 100644 atdb/taskdatabase/templates/taskdatabase/observations.html
 create mode 100644 atdb/taskdatabase/templates/taskdatabase/tasks.html

diff --git a/atdb/atdb/settings/dev.py b/atdb/atdb/settings/dev.py
index e6f7892a..785b6d74 100644
--- a/atdb/atdb/settings/dev.py
+++ b/atdb/atdb/settings/dev.py
@@ -2,7 +2,7 @@ from atdb.settings.base import *
 # SECURITY WARNING: don't run with debug turned on in production!
 DEBUG = True
 
-ALLOWED_HOSTS = ["80.101.27.83","localhost","192.168.178.32","127.0.0.1"]
+ALLOWED_HOSTS = ["*"]
 CORS_ORIGIN_ALLOW_ALL = True
 
 #####################################################
@@ -56,7 +56,3 @@ DATABASES = {
 # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
 
 AUTH_PASSWORD_VALIDATORS = []
-
-ALTA_HOST = "http://localhost:8000/altapi"
-ALTA_USER = "atdb_write"
-ALTA_PASS = "7VVJruVn8W1n"
\ No newline at end of file
diff --git a/atdb/taskdatabase/admin.py b/atdb/taskdatabase/admin.py
index d38dc14d..f9d8c439 100644
--- a/atdb/taskdatabase/admin.py
+++ b/atdb/taskdatabase/admin.py
@@ -1,5 +1,7 @@
 from django.contrib import admin
-from .models import Status, Observation
+from .models import Status, Task, Workflow, LogEntry
 
 admin.site.register(Status)
-admin.site.register(Observation)
\ No newline at end of file
+admin.site.register(Task)
+admin.site.register(Workflow)
+admin.site.register(LogEntry)
\ No newline at end of file
diff --git a/atdb/taskdatabase/config.py b/atdb/taskdatabase/config.py
index 86e4899a..f4ff5fc8 100644
--- a/atdb/taskdatabase/config.py
+++ b/atdb/taskdatabase/config.py
@@ -1,3 +1,3 @@
-VERSION = "Version 1.0.2 (27 oct 2018)"
-OBSERVATIONS_PER_PAGE = 50
+VERSION = "Version 1.0.0 (14 jan 2020)"
+TASKS_PER_PAGE = 50
 
diff --git a/atdb/taskdatabase/migrations/0001_initial.py b/atdb/taskdatabase/migrations/0001_initial.py
deleted file mode 100644
index c5b145fe..00000000
--- a/atdb/taskdatabase/migrations/0001_initial.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Generated by Django 3.1.4 on 2020-12-22 13:24
-
-import datetime
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='TaskObject',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(default='unknown', max_length=100)),
-                ('task_type', models.CharField(default='dataproduct', max_length=20)),
-                ('taskID', models.CharField(blank=True, db_index=True, max_length=30, null=True, verbose_name='runId')),
-                ('creationTime', models.DateTimeField(blank=True, default=datetime.datetime.utcnow)),
-                ('new_status', models.CharField(default='defined', max_length=50, null=True)),
-                ('data_location', models.CharField(blank=True, default='unknown', max_length=255, null=True)),
-                ('my_status', models.CharField(db_index=True, default='defined', max_length=50)),
-                ('node', models.CharField(blank=True, max_length=10, null=True)),
-                ('quality', models.CharField(default='unknown', max_length=30)),
-            ],
-        ),
-        migrations.CreateModel(
-            name='Observation',
-            fields=[
-                ('taskobject_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='taskdatabase.taskobject')),
-                ('starttime', models.DateTimeField(null=True, verbose_name='start time')),
-                ('endtime', models.DateTimeField(null=True, verbose_name='end time')),
-                ('observing_mode', models.CharField(default='imaging', max_length=50)),
-                ('field_name', models.CharField(max_length=50, null=True)),
-                ('field_ra', models.FloatField(null=True, verbose_name='field_ra')),
-                ('field_dec', models.FloatField(null=True, verbose_name='field_dec')),
-                ('skip_auto_ingest', models.BooleanField(default=False)),
-                ('progress', models.CharField(default='', max_length=40, null=True)),
-            ],
-            bases=('taskdatabase.taskobject',),
-        ),
-        migrations.CreateModel(
-            name='Status',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(default='unknown', max_length=50)),
-                ('timestamp', models.DateTimeField(blank=True, default=datetime.datetime.utcnow)),
-                ('taskObject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='taskdatabase.taskobject')),
-            ],
-        ),
-        migrations.CreateModel(
-            name='DataProduct',
-            fields=[
-                ('taskobject_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='taskdatabase.taskobject')),
-                ('filename', models.CharField(default='unknown', max_length=200)),
-                ('description', models.CharField(default='unknown', max_length=255)),
-                ('dataproduct_type', models.CharField(default='visibility', max_length=50, verbose_name='type')),
-                ('size', models.BigIntegerField(default=0)),
-                ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='generated_dataproducts', to='taskdatabase.observation')),
-            ],
-            bases=('taskdatabase.taskobject',),
-        ),
-    ]
diff --git a/atdb/taskdatabase/migrations/0002_taskobject_metadata.py b/atdb/taskdatabase/migrations/0002_taskobject_metadata.py
deleted file mode 100644
index 5dc2c103..00000000
--- a/atdb/taskdatabase/migrations/0002_taskobject_metadata.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 3.1.4 on 2020-12-22 13:45
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('taskdatabase', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='taskobject',
-            name='metadata',
-            field=models.JSONField(blank=True, null=True),
-        ),
-    ]
diff --git a/atdb/taskdatabase/migrations/0003_auto_20210114_1133.py b/atdb/taskdatabase/migrations/0003_auto_20210114_1133.py
deleted file mode 100644
index 51495fc7..00000000
--- a/atdb/taskdatabase/migrations/0003_auto_20210114_1133.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Generated by Django 3.1.4 on 2021-01-14 10:33
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('taskdatabase', '0002_taskobject_metadata'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='observation',
-            name='progress',
-            field=models.CharField(blank=True, default='0%', max_length=40, null=True),
-        ),
-        migrations.AlterField(
-            model_name='taskobject',
-            name='task_type',
-            field=models.CharField(default='observation', max_length=20),
-        ),
-        migrations.DeleteModel(
-            name='DataProduct',
-        ),
-    ]
diff --git a/atdb/taskdatabase/migrations/__init__.py b/atdb/taskdatabase/migrations/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/atdb/taskdatabase/models.py b/atdb/taskdatabase/models.py
index 3436af0a..ba847b76 100644
--- a/atdb/taskdatabase/models.py
+++ b/atdb/taskdatabase/models.py
@@ -6,81 +6,60 @@ from django.db.models import Sum
 # constants
 datetime_format_string = '%Y-%m-%dT%H:%M:%SZ'
 
-TASK_TYPE_OBSERVATION = 'observation'
+class Workflow(models.Model):
+    repository = models.CharField(max_length=100, blank=True, null=True, default="unknown")
+    commit_id = models.CharField(max_length=30, blank=True, null=True)
+    path = models.CharField(max_length=100, blank=True, null=True, default="unknown")
 
-TYPE_VISIBILITY = 'visibility'
+class LogEntry(models.Model):
+    step_name = models.CharField(max_length=30, blank=True, null=True, default="unknown")
 
-class TaskObject(models.Model):
-    name = models.CharField(max_length=100, default="unknown")
-    task_type = models.CharField(max_length=20, default=TASK_TYPE_OBSERVATION)
 
-    taskID = models.CharField('runId', db_index=True, max_length=30, blank=True, null=True)
+class Task(models.Model):
+    taskID = models.CharField(db_index=True, max_length=30, blank=True, null=True)
+    task_type = models.CharField(max_length=20, default="task")
+
+    project = models.CharField(max_length=100, blank=True, null=True, default="unknown")
+    sas_id = models.CharField(max_length=30, blank=True, null=True)
+    priority = models.IntegerField(default=0)
+    purge_policy = models.CharField(max_length=5, default="no", blank=True, null=True)
+    workflow_id = models.CharField(max_length=12, blank=True, null=True)
+
+    inputs = models.JSONField(null=True, blank=True)
+    outputs = models.JSONField(null=True, blank=True)
+
+    skip = models.BooleanField(default=False)
     creationTime = models.DateTimeField(default=datetime.utcnow, blank=True)
 
     new_status = models.CharField(max_length=50, default="defined", null=True)
-    data_location = models.CharField(max_length=255, default="unknown",null=True, blank=True)
-
-    # my_status is 'platgeslagen', because django-filters can not filter on a related property,
-    # and I need services to be able to filter on a status to execute their tasks.
     my_status = models.CharField(db_index=True, max_length=50,default="defined")
-    node = models.CharField(max_length=10, null=True, blank=True)
-    quality = models.CharField(max_length=30, default="unknown")
-    metadata = models.JSONField(null=True,blank=True)
+
 
     def __str__(self):
         return str(self.id)
 
+    # this translates a view-name (from urls.py) back to a url, to avoid hardcoded url's in the html templates
+    # bad : <td><a href="/atdb/observations/{{ observation.id }}/" target="_blank">{{ observation.taskID }} </a> </td>
+    # good: <td><a href="{{ observation.get_absolute_url }}" target="_blank">{{ observation.taskID }} </a> </td>
+    def get_absolute_url(self):
+        return reverse('task-detail-view-api', kwargs={'pk': self.pk})
+
 
 class Status(models.Model):
     name = models.CharField(max_length=50, default="unknown")
     timestamp = models.DateTimeField(default=datetime.utcnow, blank=True)
-    taskObject = models.ForeignKey(TaskObject, related_name='status_history', on_delete=models.CASCADE, null=False)
+    task = models.ForeignKey(Task, related_name='status_history', on_delete=models.CASCADE, null=False)
 
     @property
     def property_taskID(self):
-        return self.taskObject.taskID
+        return self.task.taskID
 
     @property
     def property_task_type(self):
-        return self.taskObject.task_type
+        return self.task.task_type
 
     # the representation of the value in the REST API
     def __str__(self):
         formatedDate = self.timestamp.strftime(datetime_format_string)
         return str(self.name)+' ('+str(formatedDate)+')'
 
-
-class Observation(TaskObject):
-    starttime = models.DateTimeField('start time', null=True)
-    endtime = models.DateTimeField('end time', null=True)
-
-    # can be used to distinguish types of observations, like for ARTS.
-    observing_mode = models.CharField(max_length=50, default="imaging")
-
-    # json object containing unmodelled parameters that are used by the 'executor' service
-    # to create the parset based on a template and these parameters
-    field_name = models.CharField(max_length=50, null=True)
-    field_ra = models.FloatField('field_ra', null = True)
-    field_dec = models.FloatField('field_dec', null = True)
-    skip_auto_ingest = models.BooleanField(default=False)
-
-    progress = models.CharField(max_length=40, default="0%", null=True, blank=True)
-
-    # this translates a view-name (from urls.py) back to a url, to avoid hardcoded url's in the html templates
-    # bad : <td><a href="/atdb/observations/{{ observation.id }}/" target="_blank">{{ observation.taskID }} </a> </td>
-    # good: <td><a href="{{ observation.get_absolute_url }}" target="_blank">{{ observation.taskID }} </a> </td>
-    def get_absolute_url(self):
-        return reverse('observation-detail-view-api', kwargs={'pk': self.pk})
-
-    @property
-    def duration(self):
-        try:
-            duration = (self.endtime - self.starttime).seconds
-        except:
-            # to prevent crash for invalid observations that do not have a starttime
-            duration = 0
-        return duration
-
-    def __str__(self):
-        return str(self.taskID)
-
diff --git a/atdb/taskdatabase/serializers.py b/atdb/taskdatabase/serializers.py
index 02d7a04c..cf8b2be4 100644
--- a/atdb/taskdatabase/serializers.py
+++ b/atdb/taskdatabase/serializers.py
@@ -1,30 +1,38 @@
 from rest_framework import serializers
-from .models import Observation, Status, TaskObject
-import logging
+from .models import Status, Task, Workflow, LogEntry
+
+
+class TaskSerializer(serializers.ModelSerializer):
+
+    status_history = serializers.StringRelatedField(
+        many=True,
+        required=False,
+    )
+
+    class Meta:
+        model = Task
+        fields = ('id','task_type','taskID',
+                  'project','sas_id','priority','purge_policy','skip','workflow_id',
+                  'my_status','new_status',
+                  'inputs','outputs','status_history')
 
-logger = logging.getLogger(__name__)
 
 class StatusSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Status
-        fields = ('id','name','timestamp','property_taskID','property_task_type')
+        fields = "__all__"
 
 
-class ObservationSerializer(serializers.ModelSerializer):
-
-    status_history = serializers.StringRelatedField(
-        many=True,
-        required=False,
-    )
+class WorkflowSerializer(serializers.ModelSerializer):
 
     class Meta:
-        model = Observation
-        fields = ('id','task_type', 'name','taskID',
-                  'field_name','field_ra','field_dec',
-                  'creationTime','starttime','endtime', 'duration', 'size',
-                  'my_status','new_status','status_history','generated_dataproducts',
-                  'data_location', 'node',
-                  'skip_auto_ingest','observing_mode',
-                  'quality','metadata','progress')
+        model = Workflow
+        fields = "__all__"
+
 
+class LogEntrySerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = LogEntry
+        fields = "__all__"
\ No newline at end of file
diff --git a/atdb/taskdatabase/services/algorithms.py b/atdb/taskdatabase/services/algorithms.py
index 6d9d0004..ba254400 100644
--- a/atdb/taskdatabase/services/algorithms.py
+++ b/atdb/taskdatabase/services/algorithms.py
@@ -8,7 +8,7 @@ import time
 import datetime
 import logging
 from .common import timeit
-from ..models import Observation
+from ..models import Task
 
 DATE_FORMAT = "%Y-%m-%d"
 TIME_FORMAT = "%Y-%m-%d %H:%M:%SZ"
@@ -16,45 +16,10 @@ DJANGO_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
 
 logger = logging.getLogger(__name__)
 
-def get_delta_in_minutes(timestamp1,timestamp2):
-    """
-    return minutes between 2 dates
-    :param timestamp1:
-    :param timestamp2:
-    :return:
-    """
-    date1 = datetime.datetime.strptime(timestamp1, TIME_FORMAT)
-    date2 = datetime.datetime.strptime(timestamp2, TIME_FORMAT)
-    minutes = (date2 - date1).total_seconds() / 60.0
-    return int(minutes)
-
-def get_minutes_left(target_time):
-    """
-    Determine how many minutes is left until the target_time is reached
-    :param target_timestring: The target time defined as string "YYYY-MM-DDThh:mm:ss"
-    :return: minutes left
-             A negative number of minutes means that the current time has already reached the target_time,
-             there is no time left.
-              A positive number of minutes means that there is still time left
-    """
-    now = datetime.datetime.utcnow()
-
-    # convert the time to the same formats
-    target_time = datetime.datetime.strptime(target_time.strftime('%Y-%m-%d %H:%M:%SZ'), TIME_FORMAT)
-
-    # Convert to Unix timestamp
-    d1_ts = time.mktime(now.timetuple())
-    d2_ts = time.mktime(target_time.timetuple())
-    minutes_left = round(((d2_ts - d1_ts) / 60), 2)
-
-    return minutes_left
-
 @timeit
 def get_next_taskid(timestamp, taskid_postfix):
     """
-    get the observation with a starttime closest to now.
-    example url to directly access this function from the REST API:
-    /atdb/get_next_observation?my_status=scheduled&observing_mode=imaging
+    generate a new taskid based on timestamp, with an optional postfix
     :param timestamp: timestamp on which the taskid is based
     :param taskid_postfix: optional addition to the tasked, like 190405001_IMG
     :return: taskid
@@ -74,7 +39,7 @@ def get_next_taskid(timestamp, taskid_postfix):
 
         # check if this taskid already exists. If it does, increase the counter and try again
         logger.info('checking taskid ' + str(taskid) + '..')
-        found = Observation.objects.filter(taskID=taskid).count()
+        found = Task.objects.filter(taskID=taskid).count()
 
         if found==0:
             return taskid
@@ -82,36 +47,4 @@ def get_next_taskid(timestamp, taskid_postfix):
     return -1
 
 
-@timeit
-def get_next_observation(my_status, observing_mode, datawriter):
-    """
-    get the observation with a starttime closest to now
-    example url to directly access this function from the REST API:
-    /atdb/get_next_observation?my_status=scheduled&observing_mode=imaging
-    :param my_status: status to search for (probably 'scheduled'
-    :param observing_mode: imaging or arts
-    :param datawriter: wcudata1 or wcudata2 (to be implemented later)
-    :return:
-    """
-
-    logger.info("get_next_observation("+my_status+','+observing_mode+','+str(datawriter)+")")
-
-    # query the Observations table
-    # disregard the observations which starttime are more than 1 minute in the past
-    # nowisch = datetime.datetime.utcnow() - datetime.timedelta(minutes=1)
-    try:
-        # observations = Observation.objects.filter(my_status=my_status).filter(observing_mode__icontains=observing_mode)
-        # nv: this should be all to enable datawriter2, but test first before activating
-        observations = Observation.objects.filter(my_status=my_status).filter(observing_mode__icontains=observing_mode).filter(data_location__icontains=datawriter)
-
-        # extract the earliest observation and its relevant properties to return to the view
-        earliest_observation = observations.earliest('starttime')
-        taskID = earliest_observation.taskID
-        minutes_left = get_minutes_left(earliest_observation.starttime)
-    except:
-        taskID = None
-        minutes_left = None
-
-    return taskID,minutes_left
-
 
diff --git a/atdb/taskdatabase/services/signals.py b/atdb/taskdatabase/services/signals.py
index 1fce99e5..2096d2dc 100644
--- a/atdb/taskdatabase/services/signals.py
+++ b/atdb/taskdatabase/services/signals.py
@@ -5,7 +5,7 @@ from django.core.signals import request_started, request_finished
 from django.contrib.auth.models import User
 from django.dispatch import receiver
 from django.contrib.contenttypes.models import ContentType
-from taskdatabase.models import TaskObject, Observation, Status
+from taskdatabase.models import Task, Status
 from . import jobs
 
 """
@@ -28,9 +28,9 @@ def request_finished_handler(sender, **kwargs):
 
 #--- Task signals-------------
 
-@receiver(pre_save, sender=Observation)
-def pre_save_observation_handler(sender, **kwargs):
-    logger.info("SIGNAL : pre_save Observation(" + str(kwargs.get('instance')) + ")")
+@receiver(pre_save, sender=Task)
+def pre_save_task_handler(sender, **kwargs):
+    logger.info("SIGNAL : pre_save Task(" + str(kwargs.get('instance')) + ")")
     handle_pre_save(sender, **kwargs)
 
 
@@ -56,7 +56,7 @@ def handle_pre_save(sender, **kwargs):
         myTaskObject.my_status = new_status
 
         # add the new to the status history by brewing a status object out of it
-        myStatus = Status(name=new_status, taskObject=myTaskObject)
+        myStatus = Status(name=new_status, task=myTaskObject)
         myStatus.save()
 
     # temporarily disconnect the post_save handler to save the dataproduct (again) and avoiding recursion.
@@ -71,15 +71,15 @@ def handle_pre_save(sender, **kwargs):
         jobs.dispatchJob(myTaskObject, new_status)
 
 
-@receiver(post_save, sender=Observation)
-def post_save_observation_handler(sender, **kwargs):
-    #logger.info("SIGNAL : post_save Observation(" + str(kwargs.get('instance')) + ")")
+@receiver(post_save, sender=Task)
+def post_save_task_handler(sender, **kwargs):
+    #logger.info("SIGNAL : post_save Task(" + str(kwargs.get('instance')) + ")")
     handle_post_save(sender, **kwargs)
 
 
 def handle_post_save(sender, **kwargs):
     """
-     pre_save handler for both Observation and Dataproduct. To create and write its initial status
+     pre_save handler for both Task. To create and write its initial status
     :param (in) sender: The model class that sends the trigger
     :param (in) kwargs: The instance of the object that sends the trigger.
     """
@@ -94,7 +94,7 @@ def handle_post_save(sender, **kwargs):
         myTaskObject.my_status = myTaskObject.new_status
 
         # add the new to the status history by brewing a status object out of it
-        myStatus = Status(name=myTaskObject.new_status, taskObject=myTaskObject)
+        myStatus = Status(name=myTaskObject.new_status, task=myTaskObject)
         myStatus.save()
 
     # temporarily disconnect the post_save handler to save the dataproduct (again) and avoiding recursion.
@@ -106,10 +106,10 @@ def handle_post_save(sender, **kwargs):
 
 def connect_signals():
     #logger.info("connect_signals")
-    pre_save.connect(pre_save_observation_handler, sender=Observation)
-    post_save.connect(post_save_observation_handler, sender=Observation)
+    pre_save.connect(pre_save_task_handler, sender=Task)
+    post_save.connect(post_save_task_handler, sender=Task)
 
 def disconnect_signals():
     #logger.info("disconnect_signals")
-    pre_save.disconnect(pre_save_observation_handler, sender=Observation)
-    post_save.disconnect(post_save_observation_handler, sender=Observation)
+    pre_save.disconnect(pre_save_task_handler, sender=Task)
+    post_save.disconnect(post_save_task_handler, sender=Task)
diff --git a/atdb/taskdatabase/templates/taskdatabase/index.html b/atdb/taskdatabase/templates/taskdatabase/index.html
index b55c96db..857c52a5 100644
--- a/atdb/taskdatabase/templates/taskdatabase/index.html
+++ b/atdb/taskdatabase/templates/taskdatabase/index.html
@@ -4,12 +4,12 @@
 
 
 <div class="container-fluid details-container">
-    <h4>Observations</h4>
+    <h4>Tasks</h4>
 
     {% include 'taskdatabase/pagination.html' %}
     <div class="row">
     <div class="col-sm-12 col-md-12 col-lg-12">
-        {% if my_observations %}
+        {% if my_tasks %}
         <div class="panel panel-success">
 
             <div class="panel-body">
@@ -18,24 +18,23 @@
                     <thead>
                     <tr>
                         <th width="3%">TaskID</th>
-                        <th width="10%">Observing Mode</th>
-                        <th>Name</th>
+                        <th width="10%">task_type</th>
+                        <th>Project</th>
                         <th>Status</th>
                         <th>Created</th>
-                        <th>Stop Time</th>
-                        <th>Size</th>
                         <th>Quality</th>
+                        <th>Size</th>
                         <th>Details</th>
                         <th>Actions</th>
                     </tr>
                     </thead>
                     <tbody>
-                    {% include 'taskdatabase/observations.html' %}
+                    {% include 'taskdatabase/tasks.html' %}
 
                     </tbody>
                 </table>
                 {% else %}
-                <p>No recent Observations.</p>
+                <p>No recent Tasks.</p>
                 {% endif %}
 
             </div>
@@ -45,7 +44,7 @@
     </div>
     {% include 'taskdatabase/pagination.html' %}
 </div>
-    <p class="footer"> Version 1.0.0 (14 jan 2021 - 11:30)
+    <p class="footer"> Version 1.0.0 (15 jan 2021 - 17:30)
     <script type="text/javascript">
         (function(seconds) {
             var refresh,
diff --git a/atdb/taskdatabase/templates/taskdatabase/observations.html b/atdb/taskdatabase/templates/taskdatabase/observations.html
deleted file mode 100644
index a3bdc922..00000000
--- a/atdb/taskdatabase/templates/taskdatabase/observations.html
+++ /dev/null
@@ -1,100 +0,0 @@
-{% load static %}
-{% for observation in my_observations %}
-    {% if observation.my_status != "removed_invisible" %}
-    <div class="row">
-        <tr class="{{ observation.my_status }}">
-
-
-            <td>
-                <a href="{{ observation.get_absolute_url }}" target="_blank">{{ observation.taskID }} </a>
-            </td>
-
-            <td>
-                {{ observation.observing_mode }}
-                {% if not observation.science_observation  %}
-                   &nbsp;(sys)
-                {% endif %}
-            </td>
-
-            <td>{{ observation.name }}</td>
-            <td>
-            {% if observation.my_status != "valid_priority" %}
-               {{ observation.my_status }}
-            {% endif %}
-            {% if observation.my_status == "valid_priority" %}
-                valid (priority)
-            {% endif %}
-             {% if observation.my_status == "ingesting" %}
-                {% if observation.control_parameters != "unknown" %}
-                    {{ observation.control_parameters }}
-                {% endif %}
-            {% endif %}
-            </td>
-            <td>{{ observation.creationTime|date:"Y-m-d H:i:s" }} </td>
-            <td>{{ observation.endtime|date:"Y-m-d H:i:s" }} </td>
-
-            <td>{{ observation.size|filesizeformat }} </td>
-            <td>
-                {% if "removed" in observation.my_status and observation.quality != "unknown" %}
-                    {{ observation.quality }}
-                {% endif %}
-            </td>
-            <td>
-                {% if observation.my_status == "ingesting" %}
-                <a href="https://alta.astron.nl/science/monitor" class="btn btn-primary btn-sm" target="_blank" role="button">Monitor</a>
-                {% endif %}
-
-                {% if not "bad" in observation.quality %}
-                    {% if observation.my_status == "archived" or observation.my_status == "removed" %}
-                    <a href="https://alta.astron.nl/science/details/{{ observation.taskID }}" class="btn btn-primary btn-sm" target="_blank" role="button">Monitor</a>
-                    {% endif %}
-                {% endif %}
-
-            </td>
-            <td>
-
-                {% if observation.my_status == "defined" %}
-                    <a href="{% url 'observation-setstatus-view' observation.pk 'scheduled' my_observations.number %}" class="btn btn-primary btn-sm" role="button">Schedule</a>
-                {% endif %}
-
-                {% if observation.my_status == "scheduled" %}
-                    <a href="{% url 'observation-setstatus-view' observation.pk 'defined' my_observations.number %}" class="btn btn-primary btn-sm" role="button">Unschedule</a>
-                    {% if not observation.skip_auto_ingest %}
-                       <a href="{% url 'observation-skipautoingest-view' observation.pk 'true' my_observations.number %}" class="btn btn-primary btn-sm" role="button">Skip Ingest</a>
-                    {% endif %}
-                {% endif %}
-
-                {% if observation.my_status == "completed" or observation.my_status == "incomplete" %}
-                   <a href="{% url 'observation-setstatus-view' observation.pk 'valid' my_observations.number %}" class="btn btn-primary btn-sm" role="button">Ready to Ingest</a>
-                {% endif %}
-
-                {% if observation.my_status == "error (no completing)" or observation.my_status == "defined" %}
-                   <a href="{% url 'observation-setstatus-view' observation.pk 'completing' my_observations.number %}" class="btn btn-success btn-sm" role="button">Completing</a>
-                {% endif %}
-
-                {% if observation.my_status == "valid" %}
-                   <a href="{% url 'observation-setstatus-view' observation.pk 'valid_priority' my_observations.number %}" class="btn btn-warning btn-sm" role="button">Priority Ingest</a>
-                   <a href="{% url 'observation-setstatus-view' observation.pk 'completed' my_observations.number %}" class="btn btn-primary btn-sm" role="button">Cancel Ingest</a>
-                {% endif %}
-
-                {% if observation.my_status == "valid_priority" %}
-                   <a href="{% url 'observation-setstatus-view' observation.pk 'valid' my_observations.number %}" class="btn btn-primary btn-sm" role="button">Cancel Priority</a>
-                   <a href="{% url 'observation-setstatus-view' observation.pk 'completed' my_observations.number %}" class="btn btn-primary btn-sm" role="button">Cancel Ingest</a>
-                {% endif %}
-
-                {% if observation.my_status == "archived" %}
-                    <a href="{% url 'observation-setquality-view' observation.pk 'data_is_good' my_observations.number %}" class="btn btn-success btn-sm" role="button">Data is Good</a>
-                    <a href="{% url 'observation-setquality-view' observation.pk 'data_is_bad' my_observations.number %}" class="btn btn-danger btn-sm" role="button">Data is Bad</a>
-                {% endif %}
-
-                {% if observation.my_status == "removed" and observation.quality == "unknown" %}
-                    <a href="{% url 'observation-setquality-view' observation.pk 'data_is_good' my_observations.number %}" class="btn btn-success btn-sm" role="button">Data is Good</a>
-                    <a href="{% url 'observation-setquality-view' observation.pk 'data_is_bad' my_observations.number %}" class="btn btn-danger btn-sm" role="button">Data is Bad</a>
-                {% endif %}
-
-            </td>
-            </tr>
-        </div>
-    {% endif %}
-{% endfor %}
-
diff --git a/atdb/taskdatabase/templates/taskdatabase/pagination.html b/atdb/taskdatabase/templates/taskdatabase/pagination.html
index e587632c..56987349 100644
--- a/atdb/taskdatabase/templates/taskdatabase/pagination.html
+++ b/atdb/taskdatabase/templates/taskdatabase/pagination.html
@@ -1,5 +1,5 @@
 {% load bootstrap_pagination %}
 
 <div>
-  {% bootstrap_paginate my_observations range=20 show_prev_next="true" show_first_last="true" previous_label="Previous" first_label="First" next_label="Next" last_label="Last" %}
+  {% bootstrap_paginate my_tasks range=20 show_prev_next="true" show_first_last="true" previous_label="Previous" first_label="First" next_label="Next" last_label="Last" %}
 </div>
\ No newline at end of file
diff --git a/atdb/taskdatabase/templates/taskdatabase/tasks.html b/atdb/taskdatabase/templates/taskdatabase/tasks.html
new file mode 100644
index 00000000..d451b907
--- /dev/null
+++ b/atdb/taskdatabase/templates/taskdatabase/tasks.html
@@ -0,0 +1,86 @@
+{% load static %}
+{% for task in my_tasks %}
+    {% if task.my_status != "removed_invisible" %}
+    <div class="row">
+        <tr class="{{ task.my_status }}">
+
+
+            <td>
+                <a href="{{ task.get_absolute_url }}" target="_blank">{{ task.taskID }} </a>
+            </td>
+
+            <td>
+                {{ task.task_type }}
+            </td>
+
+            <td>{{ task.project }}</td>
+            <td>
+            {% if task.my_status != "valid_priority" %}
+               {{ task.my_status }}
+            {% endif %}
+            {% if task.my_status == "valid_priority" %}
+                valid (priority)
+            {% endif %}
+             {% if task.my_status == "ingesting" %}
+                {% if task.control_parameters != "unknown" %}
+                    {{ task.control_parameters }}
+                {% endif %}
+            {% endif %}
+            </td>
+            <td>{{ task.creationTime|date:"Y-m-d H:i:s" }} </td>
+            <td>{{ task.endtime|date:"Y-m-d H:i:s" }} </td>
+
+            <td>{{ task.size|filesizeformat }} </td>
+            <td>
+                {% if "removed" in task.my_status and task.quality != "unknown" %}
+                    {{ task.quality }}
+                {% endif %}
+            </td>
+
+            <td>
+
+                {% if task.my_status == "defined" %}
+                    <a href="{% url 'task-setstatus-view' task.pk 'scheduled' my_tasks.number %}" class="btn btn-primary btn-sm" role="button">Schedule</a>
+                {% endif %}
+
+                {% if task.my_status == "scheduled" %}
+                    <a href="{% url 'task-setstatus-view' task.pk 'defined' my_tasks.number %}" class="btn btn-primary btn-sm" role="button">Unschedule</a>
+                    {% if not task.skip_auto_ingest %}
+                       <a href="{% url 'task-skipautoingest-view' task.pk 'true' my_tasks.number %}" class="btn btn-primary btn-sm" role="button">Skip Ingest</a>
+                    {% endif %}
+                {% endif %}
+
+                {% if task.my_status == "completed" or task.my_status == "incomplete" %}
+                   <a href="{% url 'task-setstatus-view' task.pk 'valid' my_tasks.number %}" class="btn btn-primary btn-sm" role="button">Ready to Ingest</a>
+                {% endif %}
+
+                {% if task.my_status == "error (no completing)" or task.my_status == "defined" %}
+                   <a href="{% url 'task-setstatus-view' task.pk 'completing' my_tasks.number %}" class="btn btn-success btn-sm" role="button">Completing</a>
+                {% endif %}
+
+                {% if task.my_status == "valid" %}
+                   <a href="{% url 'task-setstatus-view' task.pk 'valid_priority' my_tasks.number %}" class="btn btn-warning btn-sm" role="button">Priority Ingest</a>
+                   <a href="{% url 'task-setstatus-view' task.pk 'completed' my_tasks.number %}" class="btn btn-primary btn-sm" role="button">Cancel Ingest</a>
+                {% endif %}
+
+                {% if task.my_status == "valid_priority" %}
+                   <a href="{% url 'task-setstatus-view' task.pk 'valid' my_tasks.number %}" class="btn btn-primary btn-sm" role="button">Cancel Priority</a>
+                   <a href="{% url 'task-setstatus-view' task.pk 'completed' my_tasks.number %}" class="btn btn-primary btn-sm" role="button">Cancel Ingest</a>
+                {% endif %}
+
+                {% if task.my_status == "archived" %}
+                    <a href="{% url 'task-setquality-view' task.pk 'data_is_good' my_tasks.number %}" class="btn btn-success btn-sm" role="button">Data is Good</a>
+                    <a href="{% url 'task-setquality-view' task.pk 'data_is_bad' my_tasks.number %}" class="btn btn-danger btn-sm" role="button">Data is Bad</a>
+                {% endif %}
+
+                {% if task.my_status == "removed" and task.quality == "unknown" %}
+                    <a href="{% url 'task-setquality-view' task.pk 'data_is_good' my_tasks.number %}" class="btn btn-success btn-sm" role="button">Data is Good</a>
+                    <a href="{% url 'task-setquality-view' task.pk 'data_is_bad' my_tasks.number %}" class="btn btn-danger btn-sm" role="button">Data is Bad</a>
+                {% endif %}
+
+            </td>
+            </tr>
+        </div>
+    {% endif %}
+{% endfor %}
+
diff --git a/atdb/taskdatabase/urls.py b/atdb/taskdatabase/urls.py
index 1d62b4d6..56ebc80a 100644
--- a/atdb/taskdatabase/urls.py
+++ b/atdb/taskdatabase/urls.py
@@ -4,19 +4,18 @@ from . import views
 
 urlpatterns = [
     # --- GUI ---
-    # ex: /atdb/
-    #path('', views.index, name='index'),
+
     path('', views.IndexView.as_view(), name='index'),
 
     # --- REST API ---
-    # ex: /atdb/observations/
-    path('observations/', views.ObservationListViewAPI.as_view()),
+    path('tasks/', views.TaskListViewAPI.as_view()),
+    path('tasks/<int:pk>/', views.TaskDetailsViewAPI.as_view(), name='task-detail-view-api'),
 
-    # ex: /atdb/observations_unpaginated/
-    path('observations_unpaginated/', views.ObservationListUnpaginatedViewAPI.as_view()),
+    path('workflows/', views.WorkflowListViewAPI.as_view()),
+    path('workflows/<int:pk>/', views.WorkflowDetailsViewAPI.as_view(), name='workflow-detail-view-api'),
 
-    # ex: /atdb/observations/5/
-    path('observations/<int:pk>/', views.ObservationDetailsViewAPI.as_view(),name='observation-detail-view-api'),
+    path('logentries/', views.LogEntryListViewAPI.as_view()),
+    path('logentries/<int:pk>/', views.LogEntryDetailsViewAPI.as_view(), name='logentry-detail-view-api'),
 
     # --- custom requests ---
     # ex: /atdb/get_next_taskid?timestamp=2019-04-05
@@ -24,24 +23,12 @@ urlpatterns = [
          views.GetNextTaskIDView.as_view(),
          name='get-next-taskid-view'),
 
-    # ex: /atdb/get_next_observation?my_status=scheduled&observing_mode=imaging
-    path('get_next_observation',
-         views.GetNextObservationView.as_view(),
-         name='get-next-observation-view'),
-
     # --- controller resources ---
-    path('observations/<int:pk>/setstatus/<new_status>/<page>',
-         views.ObservationSetStatus,
-         name='observation-setstatus-view'),
-
-    # set the quality field to 'good' or 'bad' (and transmit it to ALTA)
-    path('observations/<int:pk>/setquality/<quality>/<page>',
-         views.ObservationSetQuality,
-         name='observation-setquality-view'),
-    path('observations/<int:pk>/setdatawriter/<datawriter>/<page>',
-         views.SetDatawriter,
-         name='observation-setdatawriter-view'),
-    path('observations/<int:pk>/skipautoingest/<skip_it>/<page>',
-         views.SkipAutoIngest,
-         name='observation-skipautoingest-view'),
+    path('tasks/<int:pk>/setstatus/<new_status>/<page>',
+         views.TaskSetStatus,
+         name='task-setstatus-view'),
+
+    path('tasks/<int:pk>/skip/<skip_it>/<page>',
+         views.Skip,
+         name='task-skip-view'),
 ]
\ No newline at end of file
diff --git a/atdb/taskdatabase/views.py b/atdb/taskdatabase/views.py
index 64165189..b5bfefe9 100644
--- a/atdb/taskdatabase/views.py
+++ b/atdb/taskdatabase/views.py
@@ -9,274 +9,209 @@ from django.views.generic import ListView
 from rest_framework import generics, pagination
 from rest_framework.response import Response
 from django_filters import rest_framework as filters
-from django.template import loader
 from django.shortcuts import render, redirect
 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
 
-from .models import Observation, Status
+from .models import Task, Status, Workflow, LogEntry
 from django.db.models import Q
-from .serializers import ObservationSerializer, StatusSerializer
+from .serializers import TaskSerializer, WorkflowSerializer, LogEntrySerializer
 from .forms import FilterForm
 
 from .services import algorithms
-from .services.common import timeit
 
 logger = logging.getLogger(__name__)
 
-
 # ---------- filters (in the REST API) ---------
-# example: /atdb/observations/?observing_mode__icontains=arts
-class ObservationFilter(filters.FilterSet):
-    # A direct filter on a @property field is not possible, this simulates that behaviour
+class TaskFilter(filters.FilterSet):
+
     class Meta:
-        model = Observation
+        model = Task
 
         fields = {
-            'observing_mode': ['exact', 'in', 'icontains', 'startswith'],  # /atdb/observations/?observing_mode__icontains=arts
-            'field_ra': ['gt', 'lt', 'gte', 'lte', 'contains', 'exact'],
-            'field_dec': ['gt', 'lt', 'gte', 'lte', 'contains', 'exact'],
-            'name': ['exact', 'icontains'],
-            'my_status': ['exact', 'icontains', 'in', 'startswith'],          #/atdb/observations?&my_status__in=archived,removing
+            'project': ['exact', 'icontains'],
+            'sas_id': ['exact', 'icontains'],
+            'my_status': ['exact', 'icontains', 'in', 'startswith'],
             'taskID': ['gt', 'lt', 'gte', 'lte','exact', 'icontains', 'startswith','in'],
-            'creationTime' : ['gt', 'lt', 'gte', 'lte', 'contains', 'exact'],
-            'starttime' : ['gt', 'lt', 'gte', 'lte', 'contains', 'exact'],
-            'endtime': ['gt', 'lt', 'gte', 'lte', 'contains', 'exact'],
-            'data_location': ['exact', 'icontains'],
-            'node': ['exact', 'in'],
-            # 'metadata': ['icontains'],
-            'skip_auto_ingest': ['exact'],
-            'quality': ['exact', 'icontains'],
+            'purge_policy': ['exact'],
+            'priority': ['exact'],
         }
 
-
-# example: has 1811130001 been on 'running?'
-# http://localhost:8000/atdb/status/?&taskID=181130001&name=running
-class StatusFilter(filters.FilterSet):
-
-    # A direct filter on a @property field is not possible, this simulates that behaviour
-    taskID = filters.Filter(field_name="taskObject__taskID",lookup_expr='exact')
+class WorkflowFilter(filters.FilterSet):
 
     class Meta:
-        model = Status
+        model = Workflow
 
-        # https://django-filter.readthedocs.io/en/master/ref/filters.html?highlight=exclude
         fields = {
-            #'taskid': ['exact', 'in'],
-            'name': ['exact', 'in'],
-            'timestamp': ['gt', 'lt', 'gte', 'lte', 'contains', 'exact'],
-            'taskObject__taskID': ['exact', 'in'],
-            # 'taskID': ['exact', 'in'],
-
+            'repository': ['exact', 'icontains'],
+            'commit_id': ['exact', 'icontains'],
+            'path': ['exact', 'icontains'],
         }
 
-# this uses a form
-def do_filter(request):
 
-    if request.method == 'GET':
-        # create a form instance and populate it with data from the request:
-        form = FilterForm(request.POST)
-        if form.is_valid():
-            status = form.cleaned_data.get('status')
-            #observations = get_filtered_observations()
-            return HttpResponseRedirect('/atdb/')
-    else:
-        form = FilterForm
+class LogEntryFilter(filters.FilterSet):
 
-    return render(request, 'taskdatabase/index.html', {'my_form': form})
+    class Meta:
+        model = LogEntry
+
+        fields = {
+            'step_name': ['exact', 'icontains'],
+        }
 
 # ---------- GUI Views -----------
-# http://localhost:8000/atdb/
-# calling this view renders the index.html template in the GUI (the observation list)
-# http://localhost:8000/atdb/query?my_status=removed
-# http://localhost:8000/atdb/query?not_my_status=removed
 
 class IndexView(ListView):
     """
-    This is the main view of ATDB. It shows a pagination list of observations, sorted by creationTime.
+    This is the main view of ATDB. It shows a pagination list of tasks, sorted by creationTime.
     """
     template_name = 'taskdatabase/index.html'
 
     # by default this returns the list in an object called object_list, so use 'object_list' in the html page.
     # but if 'context_object_name' is defined, then this returned list is named and can be accessed that way in html.
-    context_object_name = 'my_observations'
+    context_object_name = 'my_tasks'
 
     def get_queryset(self):
-        #observations = Observation.objects.order_by('-creationTime')
-        #observations = get_filtered_observations()
         my_status = self.request.GET.get('my_status')
         not_my_status = self.request.GET.get('not_my_status')
         search_box = self.request.GET.get('search_box', None)
 
         if (search_box is not None):
-            observations = get_searched_observations(search_box)
+            tasks = get_searched_tasks(search_box)
         else:
-            #observations = Observation.objects.order_by('-taskID')
-            observations = Observation.objects.order_by('-creationTime')
+             tasks = Task.objects.order_by('-creationTime')
         if (my_status is not None):
-            observations = get_filtered_observations(my_status)
+            tasks = get_filtered_tasks(my_status)
         if (not_my_status is not None):
-            observations = get_unfiltered_observations(not_my_status)
+            tasks = get_unfiltered_tasks(not_my_status)
 
-        paginator = Paginator(observations, config.OBSERVATIONS_PER_PAGE)  # Show 50 observations per page
+        paginator = Paginator(tasks, config.TASKS_PER_PAGE)  # Show 50 tasks per page
 
         page = self.request.GET.get('page')
 
         try:
-            observations = paginator.page(page)
+            tasks = paginator.page(page)
         except PageNotAnInteger:
             # If page is not an integer, deliver first page.
-            observations = paginator.page(1)
+            tasks = paginator.page(1)
         except EmptyPage:
             # If page is out of range (e.g. 9999), deliver last page of results.
-            observations = paginator.page(paginator.num_pages)
+            tasks = paginator.page(paginator.num_pages)
 
-        return observations
+        return tasks
 
 # an attempt to get a filtering mechanism in the GUI
 # filter on a single status
 # http://localhost:8000/atdb/query?my_status=scheduled
-def get_filtered_observations(my_status):
-    q = Observation.objects.order_by('-creationTime')
+def get_filtered_tasks(my_status):
+    q = Task.objects.order_by('-creationTime')
     q = q.filter(my_status=my_status)
     #q = q.exclude(my_status__icontains='removed')
     return q
 
 # http://localhost:8000/atdb/query?not_my_status=removed
-def get_unfiltered_observations(my_status):
-    q = Observation.objects.order_by('-creationTime')
+def get_unfiltered_tasks(my_status):
+    q = Task.objects.order_by('-creationTime')
     q = q.exclude(my_status=my_status)
     return q
 
-def get_searched_observations(search):
-    observations = Observation.objects.filter(
+def get_searched_tasks(search):
+    tasks = Task.objects.filter(
         Q(taskID__contains=search) |
-        Q(observing_mode__icontains=search) |
+        Q(sas_id__contains=search) |
+        Q(task_type__icontains=search) |
         Q(my_status__icontains=search) |
-        Q(field_name__icontains=search)).order_by('-creationTime')
-    return observations
+        Q(project__icontains=search)).order_by('-creationTime')
+    return tasks
 
 
 # ---------- REST API views -----------
 
-
-# example: /atdb/observations/
-# calling this view serializes the observations list in a REST API
-class ObservationListViewAPI(generics.ListCreateAPIView):
+# example: /atdb/tasks/
+class TaskListViewAPI(generics.ListCreateAPIView):
     """
-    A pagination list of observations, unsorted.
+    A pagination list of tasks, unsorted.
     """
-    model = Observation
-    queryset = Observation.objects.all()
-    serializer_class = ObservationSerializer
+    model = Task
+    queryset = Task.objects.all()
+    serializer_class = TaskSerializer
 
     # using the Django Filter Backend - https://django-filter.readthedocs.io/en/latest/index.html
     filter_backends = (filters.DjangoFilterBackend,)
-    filter_class = ObservationFilter
+    filter_class = TaskFilter
 
-class ObservationListUnpaginated(pagination.PageNumberPagination):
-    page_size = 10000
 
-# example: /atdb/observations_unpaginated/
-# calling this view serializes the observations list in a REST API
-class ObservationListUnpaginatedViewAPI(generics.ListCreateAPIView):
+# example: /atdb/tasks/5/
+# calling this view serializes a task in the REST API
+class TaskDetailsViewAPI(generics.RetrieveUpdateDestroyAPIView):
     """
-    A pagination list of observations, unsorted.
+    Detailed view of a task.
     """
-    model = Observation
-    queryset = Observation.objects.all()
-    serializer_class = ObservationSerializer
-    pagination_class = ObservationListUnpaginated
+    model = Task
+    queryset = Task.objects.all()
+    serializer_class = TaskSerializer
 
-    # using the Django Filter Backend - https://django-filter.readthedocs.io/en/latest/index.html
-    filter_backends = (filters.DjangoFilterBackend,)
-    filter_class = ObservationFilter
 
+# example: /atdb/workflows/
+class WorkflowListViewAPI(generics.ListCreateAPIView):
+    model = Workflow
+    queryset = Workflow.objects.all()
+    serializer_class = WorkflowSerializer
 
-# example: /atdb/observations/5/
-# calling this view serializes an observation in the REST API
-class ObservationDetailsViewAPI(generics.RetrieveUpdateDestroyAPIView):
-    """
-    Detailed view of an observation.
-    """
-    model = Observation
-    queryset = Observation.objects.all()
-    serializer_class = ObservationSerializer
+    filter_backends = (filters.DjangoFilterBackend,)
+    filter_class = WorkflowFilter
 
 
-# --- controller resources, triggered by a button in the GUI or directoy with a URL ---
-# set observation status to 'new_status' - called from the GUI
-# example: 'Schedule', 'Unschedule', 'Ready to Ingest', 'Remove Data'
+# example: /atdb/workflows/5/
+class WorkflowDetailsViewAPI(generics.RetrieveUpdateDestroyAPIView):
+    model = Workflow
+    queryset = Workflow.objects.all()
+    serializer_class = WorkflowSerializer
 
-def ObservationSetQuality(request,pk,quality,page):
-    model = Observation
-    observation = Observation.objects.get(pk=pk)
-    observation.quality = quality
 
-    # send quality to ALTA
-    #taskid = observation.taskID
-    #result = algorithms.send_quality_to_alta(taskid,quality)
+# example: /atdb/logentries/
+class LogEntryListViewAPI(generics.ListCreateAPIView):
+    model = LogEntry
+    queryset = LogEntry.objects.all()
+    serializer_class = LogEntrySerializer
 
-    # nv:24jan2020, no longer communicate with ALTA directly, but leave it to a atdb service to pick up.
-    observation.quality = quality
-    observation.save()
-    result = "OK"
+    filter_backends = (filters.DjangoFilterBackend,)
+    filter_class = WorkflowFilter
 
-    if result == "OK":
 
-        # only save the observation when the update to ALTA has succeeded
-        # removed => removed (bad) or removed (good)
-        # observation.new_status = observation.my_status+' ('+observation.quality+')'
+# example: /atdb/workflows/5/
+# calling this view serializes a task in the REST API
+class LogEntryDetailsViewAPI(generics.RetrieveUpdateDestroyAPIView):
+    """
+    Detailed view of a LogEntry.
+    """
+    model = LogEntry
+    queryset = LogEntry.objects.all()
+    serializer_class = LogEntrySerializer
 
-        observation.save()
-        return redirect('/atdb/?page='+page)
-    else:
-        # todo: some better HTML error handling
-        return redirect('/atdb/ooops/&error='+result)
 
+# --- controller resources, triggered by a button in the GUI or directoy with a URL ---
+# set task status to 'new_status' - called from the GUI
 
-def SkipAutoIngest(request,pk,skip_it,page):
-    model = Observation
-    observation = Observation.objects.get(pk=pk)
-    observation.skip_auto_ingest = (skip_it == 'true')
 
-    observation.save()
+def Skip(request,pk,skip_it,page):
+    model = Task
+    task = Task.objects.get(pk=pk)
+    task.skip = (skip_it == 'true')
+    task.save()
     return redirect('/atdb/?page='+page)
 
 
-def ObservationSetStatus(request,pk,new_status,page):
-    model = Observation
-    observation = Observation.objects.get(pk=pk)
-    observation.new_status = new_status
-    observation.save()
-    return redirect('/atdb/?page='+page)
-
-
-# set the datawriter to which the observation will write to.
-# /atdb/observations/<int:pk>/setdatawriter/<nr>/<page>
-def SetDatawriter(request,pk,datawriter,page):
-    model = Observation
-    observation = Observation.objects.get(pk=pk)
-
-    # retrieve current data_location
-    data_location = observation.data_location
-    params = data_location.split(':')
-    host = params[0]
-    path = params[1]
-
-    # regardless of the current datawriter, apply the new one
-    new_data_location = datawriter + ":" + path
-
-    observation.data_location = new_data_location
-    observation.save()
-    taskid = observation.taskID
-
+def TaskSetStatus(request,pk,new_status,page):
+    model = Task
+    task = Task.objects.get(pk=pk)
+    task.new_status = new_status
+    task.save()
     return redirect('/atdb/?page='+page)
 
 
 # get the next taskid based on starttime and what is currently in the database
 #/atdb/get_next_taskid?timestamp=2019-04-05
 class GetNextTaskIDView(generics.ListAPIView):
-    queryset = Observation.objects.all()
+    queryset = Task.objects.all()
 
     # override list and generate a custom response
     def list(self, request, *args, **kwargs):
@@ -302,37 +237,3 @@ class GetNextTaskIDView(generics.ListAPIView):
         })
 
 
-# get the observation with a starttime closest to now.
-# /atdb/get_next_observation?my_status=scheduled&observing_mode=imaging
-class GetNextObservationView(generics.ListAPIView):
-    queryset = Observation.objects.all()
-
-    # override list and generate a custom response
-    def list(self, request, *args, **kwargs):
-
-        # read the arguments from the query
-        try:
-            my_status = self.request.query_params['my_status']
-        except:
-            my_status = None
-
-        try:
-            observing_mode = self.request.query_params['observing_mode']
-        except:
-            observing_mode = None
-
-        try:
-            datawriter = self.request.query_params['datawriter']
-        except:
-            datawriter = None
-
-        # call the business logic
-        taskID, minutes_left = algorithms.get_next_observation(my_status, observing_mode, datawriter)
-
-        # return a response
-        return Response({
-            'taskID': taskID,
-            'minutes_left': minutes_left
-        })
-
-
-- 
GitLab