diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e8e336c5fc789a9bc47574134599f41dea94eef5..7f96632c36e9b1caa7887e0fb323adc3fcf69678 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -265,6 +265,7 @@ integration_test_TMSS:
     RABBITMQ_DEFAULT_PASS: guest
     LOFAR_DEFAULT_BROKER: 'rabbitmq' # override default 'localhost' which does not work for CI service rabbitmq.
   needs:
+    - build_TMSS
     - unit_test_TMSS
   artifacts:
     name: integration-test-report
@@ -289,6 +290,7 @@ integration_test_RAServices:
     - cd build/gnucxx11_opt
     - SKIP_INTEGRATION_TESTS=false SKIP_UNIT_TESTS=true ctest
   needs:
+    - build_RAServices
     - unit_test_RAServices
   artifacts:
     name: integration-test-report
@@ -313,6 +315,7 @@ integration_test_LTAIngest:
     RABBITMQ_DEFAULT_PASS: guest
     LOFAR_DEFAULT_BROKER: 'rabbitmq' # override default 'localhost' which does not work for CI service rabbitmq.
   needs:
+    - build_LTAIngest
     - unit_test_LTAIngest
   artifacts:
     name: integration-test-report
diff --git a/Docker/lofar-ci/Dockerfile_ci_sas b/Docker/lofar-ci/Dockerfile_ci_sas
index 527639e256c50c98b1ef0550b41a7cbf69b3e1c3..1aa8f6689b56f7529d3a0a17e0022128a9ab2bbc 100644
--- a/Docker/lofar-ci/Dockerfile_ci_sas
+++ b/Docker/lofar-ci/Dockerfile_ci_sas
@@ -16,7 +16,7 @@ RUN yum erase -y postgresql postgresql-server postgresql-devel && \
     cd /bin && ln -s /usr/pgsql-9.6/bin/initdb && ln -s /usr/pgsql-9.6/bin/postgres
 ENV PATH /usr/pgsql-9.6/bin:$PATH 
 
-RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil Django==3.0.9 djangorestframework==3.11.1 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 django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet pyxb==1.2.5 graphviz isodate astropy packaging
+RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil Django==3.0.9 djangorestframework==3.11.1 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 django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet pyxb==1.2.5 graphviz isodate astropy packaging django-debug-toolbar 
 
 #Viewflow package 
 RUN pip3 install django-material django-viewflow
diff --git a/SAS/TMSS/docker-compose-ua.yml b/SAS/TMSS/docker-compose-ua.yml
index 74752f8596f9daa35763a85b7f5e355288b38cbd..73b699d14c94f2d0606c3242d4b964f593d05b68 100644
--- a/SAS/TMSS/docker-compose-ua.yml
+++ b/SAS/TMSS/docker-compose-ua.yml
@@ -17,9 +17,10 @@ services:
       - "8088:8088"
   web:
     image: nexus.cep4.control.lofar:18080/tmss_django:latest
+    hostname: tmss-ua
     restart: on-failure
     env_file:
       - ./.env
-    command: bash -c 'source /opt/lofar/lofarinit.sh && python3 lib64/python3.6/site-packages/lofar/sas/tmss/manage.py runserver 0.0.0.0:8008'
+    command: bash -c 'source /opt/lofar/lofarinit.sh && ALLOWED_HOSTS=* tmss_test_environment -H 0.0.0.0 -P tmss-ua -p 8008 --data'
     ports:
       - "8008:8008"
diff --git a/SAS/TMSS/src/tmss/CMakeLists.txt b/SAS/TMSS/src/tmss/CMakeLists.txt
index a38c2b149ed20a69a4ae3376365d869db9c1990e..3e7754777f2f6d34a58352c9d78765303dd9cfa4 100644
--- a/SAS/TMSS/src/tmss/CMakeLists.txt
+++ b/SAS/TMSS/src/tmss/CMakeLists.txt
@@ -13,3 +13,4 @@ python_install(${_py_files}
     DESTINATION lofar/sas/tmss/tmss)
 
 add_subdirectory(tmssapp)
+add_subdirectory(workflowapp)
diff --git a/SAS/TMSS/src/tmss/settings.py b/SAS/TMSS/src/tmss/settings.py
index 3fcb6ea5e997dfabaa0357e8d62c9da6b4a54cac..97b14e0609ec957a3553493dec1a668033b7a841 100644
--- a/SAS/TMSS/src/tmss/settings.py
+++ b/SAS/TMSS/src/tmss/settings.py
@@ -120,8 +120,17 @@ INSTALLED_APPS = [
     'material.frontend',
     'viewflow',
     'viewflow.frontend',
+    'lofar.sas.tmss.tmss.workflowapp',
+    'debug_toolbar',
 ]
 
+def show_debug_toolbar(*args, **kwargs):
+    return os.environ.get('SHOW_DJANGO_DEBUG_TOOLBAR', False)
+
+DEBUG_TOOLBAR_CONFIG = {
+    'SHOW_TOOLBAR_CALLBACK': show_debug_toolbar
+}
+
 MIDDLEWARE = [
     'django.middleware.gzip.GZipMiddleware',
     'django.middleware.security.SecurityMiddleware',
@@ -133,6 +142,7 @@ MIDDLEWARE = [
     'django.middleware.clickjacking.XFrameOptionsMiddleware'
 ]
 
+
 ROOT_URLCONF = 'lofar.sas.tmss.tmss.urls'
 
 TEMPLATES = [
diff --git a/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt b/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt
index e24af6998d0ad9240a454cd41fdb389a38cb4208..58c545f7ed434d8c05064e1fad48ebf0c93d821a 100644
--- a/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt
+++ b/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt
@@ -23,5 +23,3 @@ add_subdirectory(serializers)
 add_subdirectory(viewsets)
 add_subdirectory(adapters)
 add_subdirectory(schemas)
-add_subdirectory(workflows)
-
diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py
index daa63f9369488f5e160485fbfec01af9cdb5121b..45b53b6b1f17acc72ba81dedbfe5036d1b420889 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 3.0.9 on 2020-09-24 15:47
+# Generated by Django 3.0.9 on 2020-09-30 09:15
 
 from django.conf import settings
 import django.contrib.postgres.fields
@@ -14,7 +14,6 @@ class Migration(migrations.Migration):
 
     dependencies = [
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-        ('viewflow', '0008_jsonfield_and_artifact'),
     ]
 
     operations = [
@@ -492,26 +491,6 @@ class Migration(migrations.Migration):
                 'abstract': False,
             },
         ),
-        migrations.CreateModel(
-            name='SchedulingUnitDemo',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=50)),
-                ('state', models.IntegerField()),
-            ],
-        ),
-        migrations.CreateModel(
-            name='SchedulingUnitDemoProcess',
-            fields=[
-                ('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='viewflow.Process')),
-                ('text', models.CharField(max_length=150)),
-                ('approved', models.BooleanField(default=False)),
-            ],
-            options={
-                'abstract': False,
-            },
-            bases=('viewflow.process',),
-        ),
         migrations.CreateModel(
             name='SchedulingUnitDraft',
             fields=[
@@ -764,19 +743,6 @@ class Migration(migrations.Migration):
                 'abstract': False,
             },
         ),
-        migrations.CreateModel(
-            name='HelloWorldProcess',
-            fields=[
-            ],
-            options={
-                'verbose_name': 'World Request',
-                'verbose_name_plural': 'World Requests',
-                'proxy': True,
-                'indexes': [],
-                'constraints': [],
-            },
-            bases=('viewflow.process',),
-        ),
         migrations.CreateModel(
             name='Setting',
             fields=[
@@ -1093,11 +1059,6 @@ class Migration(migrations.Migration):
             name='scheduling_set',
             field=models.ForeignKey(help_text='Set to which this scheduling unit draft belongs.', on_delete=django.db.models.deletion.CASCADE, related_name='scheduling_unit_drafts', to='tmssapp.SchedulingSet'),
         ),
-        migrations.AddField(
-            model_name='schedulingunitdemoprocess',
-            name='su',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='tmssapp.SchedulingUnitDemo'),
-        ),
         migrations.AddField(
             model_name='schedulingunitblueprint',
             name='draft',
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/CMakeLists.txt b/SAS/TMSS/src/tmss/tmssapp/models/CMakeLists.txt
index 2ac64b115ecf2f4bc700c614a3ba9572f3af6aa6..7598bc12c79161c19b95275e001a28adb92d3b56 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/CMakeLists.txt
+++ b/SAS/TMSS/src/tmss/tmssapp/models/CMakeLists.txt
@@ -5,8 +5,6 @@ set(_py_files
     __init__.py
     specification.py
     scheduling.py
-    helloworldflow.py
-    schedulingunitdemoflow.py
     )
 
 python_install(${_py_files}
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/__init__.py b/SAS/TMSS/src/tmss/tmssapp/models/__init__.py
index be7a174d740d60b255c47117cb8abfc657cc9bde..93f3c7e6d54f95c40d6d9484aad802b13f9991ba 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/__init__.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/__init__.py
@@ -1,4 +1,2 @@
 from .specification import *
-from .scheduling import *
-from .helloworldflow import *
-from .schedulingunitdemoflow import *
\ No newline at end of file
+from .scheduling import *
\ No newline at end of file
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
index a3ebd865de710e9df320248e1614f9ba4f5344da..0ca0fd3810e9afb9ceeb5ad0249f0673e9557f40 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
@@ -149,7 +149,7 @@ class Subtask(BasicCommon):
         super().__init__(*args, **kwargs)
 
         # keep original state for logging
-        self.__original_state = self.state
+        self.__original_state_id = self.state_id
 
     @staticmethod
     def _send_state_change_event_message(subtask_id:int, old_state: str, new_state: str):
@@ -189,7 +189,7 @@ class Subtask(BasicCommon):
 
         annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template')
 
-        if self.state.value == SubtaskState.Choices.SCHEDULED.value and self.__original_state.value == SubtaskState.Choices.SCHEDULING.value:
+        if self.state.value == SubtaskState.Choices.SCHEDULED.value and self.__original_state_id == SubtaskState.Choices.SCHEDULING.value:
             if self.start_time is None:
                 if self.predecessors.all().count() == 0:
                     raise SubtaskSchedulingException("Cannot schedule subtask id=%s when start time is 'None'." % (self.pk, ))
@@ -202,12 +202,12 @@ class Subtask(BasicCommon):
         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 == True:
+        if self.state_id != self.__original_state_id or creating == True:
             if self.created_or_updated_by_user is None:
                 identifier = None
             else:
                 identifier = self.created_or_updated_by_user.email
-            log_entry = SubtaskStateLog(subtask=self, old_state=self.__original_state, new_state=self.state,
+            log_entry = SubtaskStateLog(subtask=self, old_state=SubtaskState.objects.get(value=self.__original_state_id), new_state=self.state,
                                            user=self.created_or_updated_by_user, user_identifier=identifier)
             log_entry.save()
 
@@ -217,7 +217,7 @@ class Subtask(BasicCommon):
                 logger.error("Could not send state change to messagebus: %s", e)
 
             # update the previous state value
-            self.__original_state = self.state
+            self.__original_state_id = self.state_id
 
 class SubtaskStateLog(BasicCommon):
     """
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
index f6665e3da24db201901f135fc5708efe6e8f0caa..3caf5c6a31395acee91640096b4c8438ca496eaf 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
@@ -19,7 +19,7 @@ import json
 import jsonschema
 from django.urls import reverse as revese_url
 from collections import Counter
-
+from django.utils.functional import cached_property
 
 #
 # Common
@@ -41,7 +41,7 @@ class BasicCommon(Model):
 
 class NamedCommon(BasicCommon):
     name = CharField(max_length=128, help_text='Human-readable name of this object.', null=False) # todo: check if we want to have this primary_key=True
-    description = CharField(max_length=255, help_text='A longer description of this object.')
+    description = CharField(max_length=255, help_text='A longer description of this object.', blank=True)
 
     def __str__(self):
         return self.name
@@ -412,7 +412,7 @@ class Cycle(NamedCommonPK):
     start = DateTimeField(help_text='Moment at which the cycle starts, that is, when its projects can run.')
     stop = DateTimeField(help_text='Moment at which the cycle officially ends.')
 
-    @property
+    @cached_property
     def duration(self) -> datetime.timedelta:
         '''the duration of the cycle (stop-start date)'''
         return self.stop - self.start
@@ -553,19 +553,19 @@ class SchedulingUnitDraft(NamedCommon):
                 validate_json_against_schema(self.observation_strategy_template.template, self.requirements_template.schema)
 
         if self.scheduling_constraints_doc is not None and self.scheduling_constraints_template_id and self.scheduling_constraints_template.schema is not None:
-                validate_json_against_schema(self.scheduling_constraints_doc, self.scheduling_constraints_template.schema)
+            validate_json_against_schema(self.scheduling_constraints_doc, self.scheduling_constraints_template.schema)
 
         annotate_validate_add_defaults_to_doc_using_template(self, 'requirements_doc', 'requirements_template')
         annotate_validate_add_defaults_to_doc_using_template(self, 'scheduling_constraints_doc', 'scheduling_constraints_template')
         super().save(force_insert, force_update, using, update_fields)
 
-    @property
+    @cached_property
     def duration(self) -> datetime.timedelta:
         '''return the overall duration of all tasks of this scheduling unit
         '''
         return self.relative_stop_time - self.relative_start_time
 
-    @property
+    @cached_property
     def relative_start_time(self) -> datetime.timedelta:
         '''return the earliest relative start time of all tasks of this scheduling unit
         '''
@@ -575,7 +575,7 @@ class SchedulingUnitDraft(NamedCommon):
         else:
             return datetime.timedelta(seconds=0)
 
-    @property
+    @cached_property
     def relative_stop_time(self) -> datetime.timedelta:
         '''return the latest relative stop time of all tasks of this scheduling unit
         '''
@@ -597,7 +597,7 @@ class SchedulingUnitBlueprint(NamedCommon):
 
         super().save(force_insert, force_update, using, update_fields)
 
-    @property
+    @cached_property
     def duration(self) -> datetime.timedelta:
         '''return the overall duration of all tasks of this scheduling unit
         '''
@@ -606,7 +606,7 @@ class SchedulingUnitBlueprint(NamedCommon):
         else:
             return self.stop_time - self.start_time   # <- todo: do we ever want this?
 
-    @property
+    @cached_property
     def relative_start_time(self) -> datetime.timedelta:
         '''return the earliest relative start time of all tasks of this scheduling unit
         '''
@@ -616,7 +616,7 @@ class SchedulingUnitBlueprint(NamedCommon):
         else:
             return datetime.timedelta(seconds=0)
 
-    @property
+    @cached_property
     def relative_stop_time(self) -> datetime.timedelta:
         '''return the latest relative stop time of all tasks of this scheduling unit
         '''
@@ -626,7 +626,7 @@ class SchedulingUnitBlueprint(NamedCommon):
         else:
             return datetime.timedelta(seconds=0)
 
-    @property
+    @cached_property
     def start_time(self) -> datetime or None:
         '''return the earliest start time of all tasks of this scheduling unit
         '''
@@ -636,7 +636,7 @@ class SchedulingUnitBlueprint(NamedCommon):
         else:
             return None
 
-    @property
+    @cached_property
     def stop_time(self) -> datetime or None:
         '''return the latest stop time of all tasks of this scheduling unit
         '''
@@ -758,7 +758,7 @@ class TaskDraft(NamedCommon):
         annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template')
         super().save(force_insert, force_update, using, update_fields)
 
-    @property
+    @cached_property
     def successors(self) -> QuerySet:
         '''return the connect successor taskdraft(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets)
            If you want the result, add .all() like so: my_taskdraft.successors.all()
@@ -768,7 +768,7 @@ class TaskDraft(NamedCommon):
                                                       "INNER JOIN tmssapp_taskrelationdraft as task_rel on task_rel.consumer_id = successor_task.id\n"
                                                       "WHERE task_rel.producer_id = %s", params=[self.id]))
 
-    @property
+    @cached_property
     def predecessors(self) -> QuerySet:
         '''return the connect predecessor taskdraft(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets)
         If you want the result, add .all() like so: my_taskdraft.predecessors.all()
@@ -778,26 +778,26 @@ class TaskDraft(NamedCommon):
                                                       "INNER JOIN tmssapp_taskrelationdraft as task_rel on task_rel.producer_id = successor_task.id\n"
                                                       "WHERE task_rel.consumer_id = %s", params=[self.id]))
 
-    @property
+    @cached_property
     def duration(self) -> datetime.timedelta:
          '''returns the overall duration of this task
          '''
          return self.relative_stop_time - self.relative_start_time
 
-    @property
+    @cached_property
     def relative_start_time(self) -> datetime.timedelta:
         '''return the earliest relative start time of all subtasks of this task
         '''
         scheduling_relations = list(self.first_to_connect.all()) + list(self.second_to_connect.all())
         for scheduling_relation in scheduling_relations:
-            if scheduling_relation.first.id == self.id and scheduling_relation.placement.value == "after":
+            if scheduling_relation.first.id == self._id and scheduling_relation.placement_id == "after":
                 previous_related_task_draft = TaskDraft.objects.get(id=scheduling_relation.second.id)
                 time_offset = scheduling_relation.time_offset
                 # todo: max of several relations
                 if previous_related_task_draft.relative_stop_time:
                     return previous_related_task_draft.relative_stop_time + datetime.timedelta(seconds=time_offset)
 
-            if scheduling_relation.second.id == self.id and scheduling_relation.placement.value == "before":
+            if scheduling_relation.second.id == self._id and scheduling_relation.placement_id == "before":
                 previous_related_task_draft = TaskDraft.objects.get(id=scheduling_relation.first.id)
                 time_offset = scheduling_relation.time_offset
                 # todo: max of several relations
@@ -805,7 +805,7 @@ class TaskDraft(NamedCommon):
                     return previous_related_task_draft.relative_stop_time + datetime.timedelta(seconds=time_offset)
         return datetime.timedelta(seconds=0)
 
-    @property
+    @cached_property
     def relative_stop_time(self) -> datetime.timedelta:
         '''return the latest relative stop time of all subtasks of this task
         '''
@@ -825,7 +825,7 @@ class TaskDraft(NamedCommon):
     #  Only on the blueprints, we also aggregate start_stop times as they are in the system
     #  I'll leave these code bits here for now, until we made up our minds about this, but this can probably be removed
     #
-    # @property
+    # @cached_property
     # def duration(self) -> datetime.timedelta:
     #     '''returns the overall duration in seconds of all blueprints of this task
     #     # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint?
@@ -836,7 +836,7 @@ class TaskDraft(NamedCommon):
     #     else:
     #         return self.stop_time - self.start_time
     #
-    # @property
+    # @cached_property
     # def start_time(self) -> datetime or None:
     #     '''return the earliest start time of all blueprints of this task
     #     # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint?
@@ -848,7 +848,7 @@ class TaskDraft(NamedCommon):
     #         # todo: calculate?
     #         return None
     #
-    # @property
+    # @cached_property
     # def stop_time(self) -> datetime or None:
     #     '''return the latest stop time of all blueprints of this task
     #     # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint?
@@ -872,7 +872,7 @@ class TaskBlueprint(NamedCommon):
         annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template')
         super().save(force_insert, force_update, using, update_fields)
 
-    @property
+    @cached_property
     def successors(self) -> QuerySet:
         '''return the connect successor taskblueprint(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets)
            If you want the result, add .all() like so: my_taskblueprint.successors.all()
@@ -882,7 +882,7 @@ class TaskBlueprint(NamedCommon):
                                                           "INNER JOIN tmssapp_taskrelationblueprint as task_rel on task_rel.consumer_id = successor_task.id\n"
                                                           "WHERE task_rel.producer_id = %s", params=[self.id]))
 
-    @property
+    @cached_property
     def predecessors(self) -> QuerySet:
         '''return the connect predecessor taskblueprint(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets)
         If you want the result, add .all() like so: my_taskblueprint.predecessors.all()
@@ -892,7 +892,7 @@ class TaskBlueprint(NamedCommon):
                                                           "INNER JOIN tmssapp_taskrelationblueprint as task_rel on task_rel.producer_id = predecessor_task.id\n"
                                                           "WHERE task_rel.consumer_id = %s", params=[self.id]))
 
-    @property
+    @cached_property
     def duration(self) -> datetime.timedelta:
         '''return the overall duration of this task
         '''
@@ -901,20 +901,20 @@ class TaskBlueprint(NamedCommon):
         else:
             return self.stop_time - self.start_time
 
-    @property
+    @cached_property
     def relative_start_time(self) -> datetime.timedelta:
         '''return the earliest relative start time of all subtasks of this task
         '''
         scheduling_relations = list(self.first_to_connect.all()) + list(self.second_to_connect.all())
         for scheduling_relation in scheduling_relations:
-                if scheduling_relation.first.id == self.id and scheduling_relation.placement.value == "after":
+            if scheduling_relation.first.id == self._id and scheduling_relation.placement_id == "after":   # self.id and placement.value will hit the db, this does not
                     previous_related_task_blueprint = TaskBlueprint.objects.get(id=scheduling_relation.second.id)
                     time_offset = scheduling_relation.time_offset
                     # todo: max of several relations
                     if previous_related_task_blueprint.relative_stop_time:
                         return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset)
 
-                if scheduling_relation.second.id == self.id and scheduling_relation.placement.value == "before":
+            if scheduling_relation.second.id == self._id and scheduling_relation.placement_id == "before":   # self.id and placement.value will hit the db, this does not
                     previous_related_task_blueprint = TaskBlueprint.objects.get(id=scheduling_relation.first.id)
                     time_offset = scheduling_relation.time_offset
                     # todo: max of several relations
@@ -922,7 +922,7 @@ class TaskBlueprint(NamedCommon):
                         return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset)
         return datetime.timedelta(seconds=666660)
 
-    @property
+    @cached_property
     def relative_stop_time(self) -> datetime.timedelta:
         '''return the latest relative stop time of all subtasks of this task
         '''
@@ -934,7 +934,7 @@ class TaskBlueprint(NamedCommon):
             pass
         return self.relative_start_time
 
-    @property
+    @cached_property
     def start_time(self) -> datetime or None:
         '''return the earliest start time of all subtasks of this task
         '''
@@ -944,7 +944,7 @@ class TaskBlueprint(NamedCommon):
         else:
             return None
 
-    @property
+    @cached_property
     def stop_time(self) -> datetime or None:
         '''return the latest stop time of all subtasks of this task
         '''
diff --git a/SAS/TMSS/src/tmss/tmssapp/populate.py b/SAS/TMSS/src/tmss/tmssapp/populate.py
index d9f964aae6c3d0b9ac70e02c653edcce27eb4c2a..b786248f34773046434364d3ddc887ecd6d59e3a 100644
--- a/SAS/TMSS/src/tmss/tmssapp/populate.py
+++ b/SAS/TMSS/src/tmss/tmssapp/populate.py
@@ -55,6 +55,12 @@ def populate_test_data():
             from lofar.sas.tmss.test.tmss_test_data_django_models import SchedulingSet_test_data, SchedulingUnitDraft_test_data
             from lofar.sas.tmss.tmss.tmssapp.tasks import create_task_blueprints_and_subtasks_from_scheduling_unit_draft, create_task_blueprints_and_subtasks_and_schedule_subtasks_from_scheduling_unit_draft
             from lofar.sas.tmss.tmss.tmssapp.subtasks import schedule_subtask
+            from lofar.common.json_utils import get_default_json_object_for_schema
+
+            constraints_template = models.SchedulingConstraintsTemplate.objects.get(name="constraints")
+            constraints_spec = get_default_json_object_for_schema(constraints_template.schema)
+
+            strategy_template = models.SchedulingUnitObservingStrategyTemplate.objects.get(name="UC1 CTC+pipelines")
 
             # create a Test Scheduling Set UC1 under project TMSS-Commissioning
             tmss_project = models.Project.objects.get(name="TMSS-Commissioning")
@@ -67,8 +73,6 @@ def populate_test_data():
                 logger.info('created test scheduling_set: %s', scheduling_set.name)
 
                 for unit_nr in range(5):
-                    strategy_template = models.SchedulingUnitObservingStrategyTemplate.objects.get(name="UC1 CTC+pipelines")
-
 
                     # the 'template' in the strategy_template is a predefined json-data blob which validates against the given scheduling_unit_template
                     # a user might 'upload' a partial json-data blob, so add all the known defaults
@@ -79,7 +83,9 @@ def populate_test_data():
                                                                                       scheduling_set=scheduling_set,
                                                                                       requirements_template=strategy_template.scheduling_unit_template,
                                                                                       requirements_doc=scheduling_unit_spec,
-                                                                                      observation_strategy_template=strategy_template)
+                                                                                      observation_strategy_template=strategy_template,
+                                                                                      scheduling_constraints_doc=constraints_spec,
+                                                                                      scheduling_constraints_template=constraints_template)
                     scheduling_unit_draft.tags = ["TEST", "UC1"]
                     scheduling_unit_draft.save()
 
diff --git a/SAS/TMSS/src/tmss/tmssapp/schemas/UC1-scheduling-unit-observation-strategy.json b/SAS/TMSS/src/tmss/tmssapp/schemas/UC1-scheduling-unit-observation-strategy.json
index 05b2946b839c5e1e0929d1f3622d849de8e3cb10..183f8933332dd016194b939315b4bc25a9c3d183 100644
--- a/SAS/TMSS/src/tmss/tmssapp/schemas/UC1-scheduling-unit-observation-strategy.json
+++ b/SAS/TMSS/src/tmss/tmssapp/schemas/UC1-scheduling-unit-observation-strategy.json
@@ -65,18 +65,18 @@
         "stations":["CS001"],
         "tile_beam": {
           "direction_type": "J2000",
-          "angle1": 42,
-          "angle2": 42,
-          "angle3": 42
+          "angle1": 0.42,
+          "angle2": 0.43,
+          "angle3": 0.44
         },
         "SAPs": [
           {
             "name": "target0",
             "digital_pointing": {
               "direction_type": "J2000",
-              "angle1": 24,
-              "angle2": 24,
-              "angle3": 24
+              "angle1": 0.24,
+              "angle2": 0.25,
+              "angle3": 0.26
             },
             "subbands": [
               349,
@@ -87,9 +87,9 @@
             "name": "target1",
             "digital_pointing": {
               "direction_type": "J2000",
-              "angle1": 24,
-              "angle2": 24,
-              "angle3": 24
+              "angle1": 0.27,
+              "angle2": 0.28,
+              "angle3": 0.29
             },
             "subbands": [
               349,
diff --git a/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json b/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json
index 04bb208f0b4deff2d4a7d0491ef4108afe335922..77a916705c8df50c069f5929e11fc03d5586acf7 100644
--- a/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json
+++ b/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json
@@ -1,13 +1,32 @@
 {
+  "$id":"http://tmss.lofar.org/api/schemas/schedulingconstraintstemplate/constraints/1#",
   "$schema": "http://json-schema.org/draft-06/schema#",
-  "title": "Constraints",
-  "description": "This schema defines the constraints for a scheduling unit",
+  "title": "constraints",
+  "description": "This schema defines the scheduling constraints for a scheduling unit",
   "version": 1,
   "definitions": {
     "timestamp": {
+      "description": "A timestamp defined in UTC",
       "type": "string",
-      "pattern": "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+Z",
-      "format": "datetime"
+      "pattern": "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d(\\.\\d+)?Z",
+      "format": "date-time"
+    },
+    "timewindow": {
+      "type": "object",
+      "description": "A timewindow interval: [from, to)",
+      "properties": {
+        "from": {
+          "$ref": "#/definitions/timestamp"
+        },
+        "to": {
+          "$ref": "#/definitions/timestamp"
+        }
+      },
+      "additionalProperties": false,
+      "required": [
+        "from",
+        "to"
+      ]
     },
     "distance_on_sky": {
       "type": "number",
@@ -24,7 +43,7 @@
   "properties": {
     "scheduler": {
       "name": "Scheduler",
-      "description": "Which scheduling system wiil schedule this",
+      "description": "Which scheduling system will schedule this",
       "type": "string",
       "enum": [
         "manual",
@@ -34,6 +53,7 @@
     },
     "time": {
       "type": "object",
+      "default": {},
       "properties": {
         "at": {
           "description": "Start at this moment",
@@ -51,41 +71,28 @@
           "description": "Run within one of these time windows",
           "type": "array",
           "items": {
-            "from": {
-              "$ref": "#/definitions/timestamp"
-            },
-            "to": {
-              "$ref": "#/definitions/timestamp"
-            },
-            "required": [
-              "from",
-              "to"
-            ]
+            "$ref": "#/definitions/timewindow"
           },
-          "additionalItems": false
+          "minItems":0,
+          "uniqueItems":true,
+          "default": []
         },
         "not_between": {
-          "description": "NOT run within one of these time windows",
+          "description": "Do NOT run within any of these time windows",
           "type": "array",
           "items": {
-            "from": {
-              "$ref": "#/definitions/timestamp"
-            },
-            "to": {
-              "$ref": "#/definitions/timestamp"
-            },
-            "required": [
-              "from",
-              "to"
-            ]
+            "$ref": "#/definitions/timewindow"
           },
-          "additionalItems": false
+          "minItems":0,
+          "uniqueItems":true,
+          "default": []
         }
       },
       "additionalProperties": false
     },
     "daily": {
       "type": "object",
+      "default": {},
       "properties": {
         "require_night": {
           "description": "Must run at night",
@@ -107,6 +114,7 @@
     },
     "sky": {
       "type": "object",
+      "default": {},
       "properties": {
         "min_calibrator_elevation": {
           "description": "Minimum elevation for all calibrator sources",
@@ -123,14 +131,14 @@
           "type": "object",
           "properties": {
             "from": {
-              "type": "integer",
-              "minimum": -43200,
-              "maximum": 43200
+              "type": "number",
+              "minimum": -0.20943951,
+              "maximum": 0.20943951
             },
             "to": {
-              "type": "integer",
-              "minimum": -43200,
-              "maximum": 43200
+              "type": "number",
+              "minimum": -0.20943951,
+              "maximum": 0.20943951
             }
           },
           "additionalProperties": false
@@ -157,7 +165,6 @@
       "additionalProperties": false
     }
   },
-  "additionalProperties": false,
   "required": [
     "scheduler"
   ]
diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
index dc0f28d734c988f0343ccd657564d789ca62e9ee..bf250c5a51a2781970924e9ec30eb415d147b9fe 100644
--- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
@@ -288,6 +288,7 @@ class SchedulingSetSerializer(RelationalHyperlinkedModelSerializer):
 
 class SchedulingUnitDraftSerializer(RelationalHyperlinkedModelSerializer):
     requirements_doc = JSONEditorField(schema_source="requirements_template.schema")
+    scheduling_constraints_doc = JSONEditorField(schema_source="scheduling_constraints_template.schema")
     duration = FloatDurationField(read_only=True)
 
     class Meta:
diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/CMakeLists.txt b/SAS/TMSS/src/tmss/tmssapp/viewsets/CMakeLists.txt
index 445e0bbe4672e5cdad3a5a41be8575dbf2169ff0..fc0325a523508e371b2456d96b3467274dae748d 100644
--- a/SAS/TMSS/src/tmss/tmssapp/viewsets/CMakeLists.txt
+++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/CMakeLists.txt
@@ -6,8 +6,6 @@ set(_py_files
     lofar_viewset.py
     specification.py
     scheduling.py
-    helloworldflow.py
-    schedulingunitdemoflow.py
     )
 
 python_install(${_py_files}
diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/__init__.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/__init__.py
index 882458975ee4be50507620471ed1026433ddf589..93f3c7e6d54f95c40d6d9484aad802b13f9991ba 100644
--- a/SAS/TMSS/src/tmss/tmssapp/viewsets/__init__.py
+++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/__init__.py
@@ -1,3 +1,2 @@
 from .specification import *
-from .scheduling import *
-from .schedulingunitdemoflow import *
\ No newline at end of file
+from .scheduling import *
\ No newline at end of file
diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py
index af49948d1615bac654f06ea03f55f8b09f679d6a..731dea891e03bf199867741111d93e58a53910ed 100644
--- a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py
+++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py
@@ -144,6 +144,8 @@ class SubtaskViewSet(LOFARViewSet):
     filter_class = SubTaskFilter
     ordering = ('start_time',)
 
+    queryset = queryset.prefetch_related('state')
+
     @swagger_auto_schema(auto_schema=TextPlainAutoSchema,
                          responses={200: 'A LOFAR parset for this subtask (as plain text)',
                                     403: 'forbidden',
diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py
index ce3fa163142398bbaf6ae6bf7e197b33b6311cd2..242f06d05a6084ad6f0aa64a46eec8ce75ac164d 100644
--- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py
@@ -307,6 +307,16 @@ class SchedulingUnitDraftViewSet(LOFARViewSet):
     queryset = models.SchedulingUnitDraft.objects.all()
     serializer_class = serializers.SchedulingUnitDraftSerializer
 
+    # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
+    queryset = queryset.prefetch_related('copied_from') \
+                       .prefetch_related('scheduling_unit_blueprints')\
+                       .prefetch_related('task_drafts')
+
+    # preselect all references to other models to avoid even more duplicate queries
+    queryset = queryset.select_related('copies') \
+                       .select_related('copy_reason') \
+                       .select_related('scheduling_set')
+
     @swagger_auto_schema(responses={201: 'The Created SchedulingUnitBlueprint, see Location in Response header',
                                     403: 'forbidden'},
                          operation_description="Carve SchedulingUnitDraft in stone, and make an (uneditable) blueprint out of it.")
@@ -580,6 +590,9 @@ class SchedulingUnitBlueprintViewSet(LOFARViewSet):
     queryset = models.SchedulingUnitBlueprint.objects.all()
     serializer_class = serializers.SchedulingUnitBlueprintSerializer
 
+    # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
+    queryset = queryset.prefetch_related('task_blueprints')
+
     @swagger_auto_schema(responses={201: "This SchedulingUnitBlueprint, with references to its created TaskBlueprints and (scheduled) Subtasks.",
                                     403: 'forbidden'},
                          operation_description="Create TaskBlueprint(s) for this scheduling unit, create subtasks, and schedule the ones that are not dependend on predecessors.")
@@ -633,6 +646,22 @@ class TaskDraftViewSet(LOFARViewSet):
     queryset = models.TaskDraft.objects.all()
     serializer_class = serializers.TaskDraftSerializer
 
+    # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
+    queryset = queryset.prefetch_related('first_to_connect') \
+                       .prefetch_related('second_to_connect')\
+                       .prefetch_related('produced_by')\
+                       .prefetch_related('consumed_by')\
+                       .prefetch_related('task_blueprints')\
+                       .prefetch_related('copied_from')
+
+    # prefetch nested references in reverse models to avoid duplicate lookup queries
+    queryset = queryset.prefetch_related('first_to_connect__placement') \
+                       .prefetch_related('second_to_connect__placement')
+
+    # select all references to other models to avoid even more duplicate queries
+    queryset = queryset.select_related('copies') \
+                       .select_related('copy_reason')
+
     @swagger_auto_schema(responses={201: 'The created task blueprint, see Location in Response header',
                                     403: 'forbidden'},
                          operation_description="Carve this draft task specification in stone, and make an (uneditable) blueprint out of it.")
@@ -726,6 +755,17 @@ class TaskBlueprintViewSet(LOFARViewSet):
     queryset = models.TaskBlueprint.objects.all()
     serializer_class = serializers.TaskBlueprintSerializer
 
+    # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
+    queryset = queryset.prefetch_related('first_to_connect')\
+                       .prefetch_related('second_to_connect')\
+                       .prefetch_related('produced_by')\
+                       .prefetch_related('consumed_by')\
+                       .prefetch_related('subtasks')
+
+    # prefetch nested references in reverse models to avoid duplicate lookup queries
+    queryset = queryset.prefetch_related('first_to_connect__placement') \
+                       .prefetch_related('second_to_connect__placement')
+
     @swagger_auto_schema(responses={201: "This TaskBlueprint, with it is created subtasks",
                                     403: 'forbidden'},
                          operation_description="Create subtasks.")
diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py
index 37d9d081a3a3ecbee61e876ea3ee365b7c111ace..0776702860d8c041eb193e83f2ba72b78d1d8031 100644
--- a/SAS/TMSS/src/tmss/urls.py
+++ b/SAS/TMSS/src/tmss/urls.py
@@ -23,7 +23,7 @@ from django.views.generic.base import TemplateView, RedirectView
 
 from collections import OrderedDict
 from rest_framework import routers, permissions
-from .tmssapp import viewsets, models, serializers, views, workflows
+from .tmssapp import viewsets, models, serializers, views
 from rest_framework.documentation import include_docs_urls
 from drf_yasg.views import get_schema_view
 from drf_yasg import openapi
@@ -33,6 +33,8 @@ from datetime import datetime
 from material.frontend import urls as frontend_urls
 from viewflow.flow.viewset import FlowViewSet
 
+import debug_toolbar
+
 #
 # Django style patterns
 #
@@ -63,7 +65,8 @@ urlpatterns = [
     path('schemas/<str:template>/<str:name>/<str:version>', views.get_template_json_schema, name='get_template_json_schema'), #TODO: how to make trailing slash optional?
     path('schemas/<str:template>/<str:name>/<str:version>/', views.get_template_json_schema, name='get_template_json_schema'),
     path(r'util/utc', views.utc, name="system-utc"),
-    path(r'util/lst', views.lst, name="conversion-lst")
+    path(r'util/lst', views.lst, name="conversion-lst"),
+    path('__debug__/', include(debug_toolbar.urls)),
 ]
 
 
diff --git a/SAS/TMSS/src/tmss/workflowapp/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..94b72e83e35a77ab9b16f84b7647f8ab0c8af94a
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+include(PythonInstall)
+
+set(_py_files
+    __init__.py
+    admin.py
+    apps.py
+    tests.py
+    )
+
+python_install(${_py_files}
+    DESTINATION lofar/sas/tmss/tmss/workflowapp)
+
+add_subdirectory(migrations)
+add_subdirectory(models)
+add_subdirectory(flows)
+add_subdirectory(viewsets)
diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/helloworldflow.py b/SAS/TMSS/src/tmss/workflowapp/__init__.py
similarity index 100%
rename from SAS/TMSS/src/tmss/tmssapp/viewsets/helloworldflow.py
rename to SAS/TMSS/src/tmss/workflowapp/__init__.py
diff --git a/SAS/TMSS/src/tmss/workflowapp/admin.py b/SAS/TMSS/src/tmss/workflowapp/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/SAS/TMSS/src/tmss/workflowapp/apps.py b/SAS/TMSS/src/tmss/workflowapp/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..d70dc7921a32145aa2a76285c3362041e091a358
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class WorkflowappConfig(AppConfig):
+    name = 'workflowapp'
diff --git a/SAS/TMSS/src/tmss/tmssapp/workflows/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/flows/CMakeLists.txt
similarity index 72%
rename from SAS/TMSS/src/tmss/tmssapp/workflows/CMakeLists.txt
rename to SAS/TMSS/src/tmss/workflowapp/flows/CMakeLists.txt
index 474aada33041160e598ac2b1a126d68971d75afd..769f922e4781a912f1c0488c3655f6ab61363d3a 100644
--- a/SAS/TMSS/src/tmss/tmssapp/workflows/CMakeLists.txt
+++ b/SAS/TMSS/src/tmss/workflowapp/flows/CMakeLists.txt
@@ -8,4 +8,4 @@ set(_py_files
     )
 
 python_install(${_py_files}
-    DESTINATION lofar/sas/tmss/tmss/tmssapp/workflows)
+    DESTINATION lofar/sas/tmss/tmss/workflowapp/flows)
diff --git a/SAS/TMSS/src/tmss/tmssapp/workflows/__init__.py b/SAS/TMSS/src/tmss/workflowapp/flows/__init__.py
similarity index 100%
rename from SAS/TMSS/src/tmss/tmssapp/workflows/__init__.py
rename to SAS/TMSS/src/tmss/workflowapp/flows/__init__.py
diff --git a/SAS/TMSS/src/tmss/tmssapp/workflows/helloworldflow.py b/SAS/TMSS/src/tmss/workflowapp/flows/helloworldflow.py
similarity index 97%
rename from SAS/TMSS/src/tmss/tmssapp/workflows/helloworldflow.py
rename to SAS/TMSS/src/tmss/workflowapp/flows/helloworldflow.py
index d3307efe5f773359de58c89bea4a8728fa809c05..cd7ee660823074d4a00e5dca9e87e240098442c9 100644
--- a/SAS/TMSS/src/tmss/tmssapp/workflows/helloworldflow.py
+++ b/SAS/TMSS/src/tmss/workflowapp/flows/helloworldflow.py
@@ -5,9 +5,7 @@ from viewflow.base import this, Flow
 from viewflow.compat import _
 from viewflow.flow import views as flow_views
 
-
-from lofar.sas.tmss.tmss.tmssapp import models
-
+from .. import models
 
 
 @frontend.register
diff --git a/SAS/TMSS/src/tmss/tmssapp/workflows/schedulingunitdemoflow.py b/SAS/TMSS/src/tmss/workflowapp/flows/schedulingunitdemoflow.py
similarity index 99%
rename from SAS/TMSS/src/tmss/tmssapp/workflows/schedulingunitdemoflow.py
rename to SAS/TMSS/src/tmss/workflowapp/flows/schedulingunitdemoflow.py
index a35c72db8e9430b929e9ada4f424bbf6a58527c9..0a2882d7a4550ef3ff8e60b190c4074f60356795 100644
--- a/SAS/TMSS/src/tmss/tmssapp/workflows/schedulingunitdemoflow.py
+++ b/SAS/TMSS/src/tmss/workflowapp/flows/schedulingunitdemoflow.py
@@ -8,7 +8,7 @@ from viewflow.activation import FuncActivation, ViewActivation
 from viewflow.flow.nodes import Signal
 from viewflow import mixins
 
-from lofar.sas.tmss.tmss.tmssapp import models
+from .. import models
 
 from viewflow import frontend, ThisObject
 from viewflow.activation import STATUS
diff --git a/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e95b97379265e5eb14cfd44e85357218eb63948
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py
@@ -0,0 +1,50 @@
+# Generated by Django 3.0.9 on 2020-10-01 12:30
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('viewflow', '0008_jsonfield_and_artifact'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SchedulingUnitDemo',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=50)),
+                ('state', models.IntegerField()),
+            ],
+        ),
+        migrations.CreateModel(
+            name='HelloWorldProcess',
+            fields=[
+            ],
+            options={
+                'verbose_name': 'World Request',
+                'verbose_name_plural': 'World Requests',
+                'proxy': True,
+                'indexes': [],
+                'constraints': [],
+            },
+            bases=('viewflow.process',),
+        ),
+        migrations.CreateModel(
+            name='SchedulingUnitDemoProcess',
+            fields=[
+                ('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='viewflow.Process')),
+                ('text', models.CharField(max_length=150)),
+                ('approved', models.BooleanField(default=False)),
+                ('su', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='workflowapp.SchedulingUnitDemo')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('viewflow.process',),
+        ),
+    ]
diff --git a/SAS/TMSS/src/tmss/workflowapp/migrations/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/migrations/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..158ea7946445fcee8e52b00447df80a873e98ec2
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/migrations/CMakeLists.txt
@@ -0,0 +1,8 @@
+
+include(PythonInstall)
+
+
+FILE(GLOB _py_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.py)
+
+python_install(${_py_files}
+    DESTINATION lofar/sas/tmss/tmss/workflowapp/migrations)
\ No newline at end of file
diff --git a/SAS/TMSS/src/tmss/workflowapp/migrations/__init__.py b/SAS/TMSS/src/tmss/workflowapp/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/SAS/TMSS/src/tmss/workflowapp/models/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/models/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1c94f0a15d5ade684111945ce5bb79dfe25f7a91
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/models/CMakeLists.txt
@@ -0,0 +1,11 @@
+
+include(PythonInstall)
+
+set(_py_files
+    __init__.py
+    helloworldflow.py
+    schedulingunitdemoflow.py
+    )
+
+python_install(${_py_files}
+    DESTINATION lofar/sas/tmss/tmss/workflowapp/models)
diff --git a/SAS/TMSS/src/tmss/workflowapp/models/__init__.py b/SAS/TMSS/src/tmss/workflowapp/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..45516795a25730483ebfa40c1fbdb5f533df8ebe
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/models/__init__.py
@@ -0,0 +1,2 @@
+from .helloworldflow import *
+from .schedulingunitdemoflow import *
\ No newline at end of file
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/helloworldflow.py b/SAS/TMSS/src/tmss/workflowapp/models/helloworldflow.py
similarity index 100%
rename from SAS/TMSS/src/tmss/tmssapp/models/helloworldflow.py
rename to SAS/TMSS/src/tmss/workflowapp/models/helloworldflow.py
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/schedulingunitdemoflow.py b/SAS/TMSS/src/tmss/workflowapp/models/schedulingunitdemoflow.py
similarity index 100%
rename from SAS/TMSS/src/tmss/tmssapp/models/schedulingunitdemoflow.py
rename to SAS/TMSS/src/tmss/workflowapp/models/schedulingunitdemoflow.py
diff --git a/SAS/TMSS/src/tmss/workflowapp/tests.py b/SAS/TMSS/src/tmss/workflowapp/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/SAS/TMSS/src/tmss/workflowapp/viewsets/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/viewsets/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7adc12fcf7a85912784409d17f37177986c94298
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/viewsets/CMakeLists.txt
@@ -0,0 +1,10 @@
+
+include(PythonInstall)
+
+set(_py_files
+    __init__.py
+    schedulingunitdemoflow.py
+    )
+
+python_install(${_py_files}
+    DESTINATION lofar/sas/tmss/tmss/workflowapp/viewsets)
diff --git a/SAS/TMSS/src/tmss/workflowapp/viewsets/__init__.py b/SAS/TMSS/src/tmss/workflowapp/viewsets/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b77c70aeb959e9d4f63c395fd1079cfbbe3bc078
--- /dev/null
+++ b/SAS/TMSS/src/tmss/workflowapp/viewsets/__init__.py
@@ -0,0 +1 @@
+from .schedulingunitdemoflow import *
\ No newline at end of file
diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/schedulingunitdemoflow.py b/SAS/TMSS/src/tmss/workflowapp/viewsets/schedulingunitdemoflow.py
similarity index 92%
rename from SAS/TMSS/src/tmss/tmssapp/viewsets/schedulingunitdemoflow.py
rename to SAS/TMSS/src/tmss/workflowapp/viewsets/schedulingunitdemoflow.py
index ea117c0f9c27a4324fe76c77fe1256e1b1eca446..da3dc24e15ff6f3bd93da9037101a718f4ebed66 100644
--- a/SAS/TMSS/src/tmss/tmssapp/viewsets/schedulingunitdemoflow.py
+++ b/SAS/TMSS/src/tmss/workflowapp/viewsets/schedulingunitdemoflow.py
@@ -3,7 +3,7 @@ from rest_framework import viewsets
 from rest_framework.response import Response
 from rest_framework.decorators import action
 from rest_framework.serializers import ModelSerializer
-from lofar.sas.tmss.tmss.tmssapp import models
+from lofar.sas.tmss.tmss.workflowapp import models
 
 # Create your views here.
 
diff --git a/SAS/TMSS/test/t_subtasks.py b/SAS/TMSS/test/t_subtasks.py
index ca94d9eb33be0377cbbd5aa1d3a3cb3623be411f..8d80cb4b2cd6a652d54506ca3fcc1c5a1174e153 100755
--- a/SAS/TMSS/test/t_subtasks.py
+++ b/SAS/TMSS/test/t_subtasks.py
@@ -257,15 +257,15 @@ class SubTasksCreationFromTaskBluePrintCalibrator(unittest.TestCase):
             create_observation_control_subtask_from_task_blueprint(cal_task_blueprint)
 
         cal_task_blueprint.specifications_doc['autoselect'] = False
-        cal_task_blueprint.specifications_doc['pointing']['angle1'] = 11.11
-        cal_task_blueprint.specifications_doc['pointing']['angle2'] = 22.22
+        cal_task_blueprint.specifications_doc['pointing']['angle1'] = 1.111
+        cal_task_blueprint.specifications_doc['pointing']['angle2'] = 2.222
         subtask = create_observation_control_subtask_from_task_blueprint(cal_task_blueprint)
         self.assertEqual("defined", str(subtask.state))
         self.assertEqual("observation control", str(subtask.specifications_template.name))
         self.assertEqual("observation", str(subtask.specifications_template.type))
         self.assertEqual('J2000', subtask.specifications_doc['stations']['analog_pointing']['direction_type'])
-        self.assertEqual(11.11, subtask.specifications_doc['stations']['analog_pointing']['angle1'])
-        self.assertEqual(22.22, subtask.specifications_doc['stations']['analog_pointing']['angle2'])
+        self.assertEqual(1.111, subtask.specifications_doc['stations']['analog_pointing']['angle1'])
+        self.assertEqual(2.222, subtask.specifications_doc['stations']['analog_pointing']['angle2'])
 
 
 class SubtaskInputSelectionFilteringTest(unittest.TestCase):
diff --git a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py
index 5bb9e175d4324f9ecdaa40176243dde8fa0da040..018c985f4b69f7b626564bda91f076dcc49591b9 100755
--- a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py
+++ b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py
@@ -1335,11 +1335,10 @@ class SchedulingUnitDraftTestCase(unittest.TestCase):
         GET_OK_and_assert_equal_expected_response(self, url, test_data)
 
     def test_GET_SchedulingUnitDraft_list_view_shows_entry(self):
-
         test_data_1 = SchedulingUnitDraft_test_data("scheduler unit draft one")
-        models.SchedulingUnitDraft.objects.create(**test_data_1)
+        obj = models.SchedulingUnitDraft.objects.create(**test_data_1)
         nbr_results = models.SchedulingUnitDraft.objects.count()
-        GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/scheduling_unit_draft/', test_data_1, nbr_results)
+        GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/scheduling_unit_draft/', test_data_1, nbr_results, obj.id)
 
     def test_GET_SchedulingUnitDraft_view_returns_correct_entry(self):
 
@@ -1385,8 +1384,8 @@ class SchedulingUnitDraftTestCase(unittest.TestCase):
 
         # setup
         test_data_1 = SchedulingUnitDraft_test_data("scheduler unit draft one")
-        tdt_test_data_1 = TaskDraft_test_data("task draft one")
-        tdt_test_data_2 = TaskDraft_test_data("task draft two")
+        tdt_test_data_1 = TaskDraft_test_data("task draft one of su1")
+        tdt_test_data_2 = TaskDraft_test_data("task draft two of su2 ")
         scheduling_unit_draft = models.SchedulingUnitDraft.objects.create(**test_data_1)
         task_draft_1 = models.TaskDraft.objects.create(**tdt_test_data_1)
         task_draft_1.scheduling_unit_draft = scheduling_unit_draft
@@ -1522,9 +1521,9 @@ class TaskDraftTestCase(unittest.TestCase):
     def test_GET_TaskDraft_list_view_shows_entry(self):
 
         test_data_1 = TaskDraft_test_data("task draft")
-        models.TaskDraft.objects.create(**test_data_1)
+        obj = models.TaskDraft.objects.create(**test_data_1)
         nbr_results = models.TaskDraft.objects.count()
-        GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/task_draft/', test_data_1, nbr_results)
+        GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/task_draft/', test_data_1, nbr_results, obj.id)
 
     def test_GET_TaskDraft_view_returns_correct_entry(self):
 
@@ -1540,7 +1539,7 @@ class TaskDraftTestCase(unittest.TestCase):
     def test_nested_TaskDraft_are_filtered_according_to_SchedulingUnitDraft(self):
 
         # setup
-        test_data_1 = TaskDraft_test_data("task draft one")
+        test_data_1 = TaskDraft_test_data("task draft three")
         sudt_test_data_1 = SchedulingUnitDraft_test_data("scheduling unit draft one")
         scheduling_unit_draft_1 = models.SchedulingUnitDraft.objects.create(**sudt_test_data_1)
         test_data_1 = dict(test_data_1)
@@ -1552,7 +1551,7 @@ class TaskDraftTestCase(unittest.TestCase):
     def test_TaskDraft_contains_list_of_related_TaskBlueprint(self):
 
         # setup
-        test_data_1 = TaskDraft_test_data("task draft one")
+        test_data_1 = TaskDraft_test_data("task draft four")
         tbt_test_data_1 = TaskBlueprint_test_data()
         tbt_test_data_2 = TaskBlueprint_test_data()
         task_draft = models.TaskDraft.objects.create(**test_data_1)
@@ -1569,7 +1568,7 @@ class TaskDraftTestCase(unittest.TestCase):
     def test_TaskDraft_contains_lists_of_related_TaskRelationDraft(self):
 
         # setup
-        test_data_1 = TaskDraft_test_data("task draft one")
+        test_data_1 = TaskDraft_test_data("task draft five")
         task_draft = models.TaskDraft.objects.create(**test_data_1)
 
         trdt_test_data_1 = TaskRelationDraft_test_data()
diff --git a/SAS/TMSS/test/test_utils.py b/SAS/TMSS/test/test_utils.py
index 52e18d0a8a10191285b7daaf6266fdd00768a4bc..2edeaae66b24887a9491527b23bbe6518f4456ae 100644
--- a/SAS/TMSS/test/test_utils.py
+++ b/SAS/TMSS/test/test_utils.py
@@ -214,7 +214,7 @@ class TMSSDjangoServerInstance():
 
         # wait for server to be up and running....
         # or exit via TimeoutError
-        self.check_running_server(timeout=30)
+        self.check_running_server(timeout=60)
 
     def stop(self):
         '''
diff --git a/SAS/TMSS/test/testdata/example_UC1_scheduling_unit.json b/SAS/TMSS/test/testdata/example_UC1_scheduling_unit.json
index 639ad9535ae620604a82c8bdb9752c3a253d5618..38dc23b9cc5e09253801bbce32c50273cc05b8af 100644
--- a/SAS/TMSS/test/testdata/example_UC1_scheduling_unit.json
+++ b/SAS/TMSS/test/testdata/example_UC1_scheduling_unit.json
@@ -69,16 +69,16 @@
         "stations": ["CS001","CS002","CS003"],
         "tile_beam": {
           "direction_type": "J2000",
-          "angle1": 42,
-          "angle2": 42
+          "angle1": 0.42,
+          "angle2": 0.43
         },
         "SAPs": [
           {
             "name": "target0",
             "digital_pointing": {
               "direction_type": "J2000",
-              "angle1": 24,
-              "angle2": 24
+              "angle1": 0.24,
+              "angle2": 0.25
             },
             "subbands": [
               349,
diff --git a/SAS/TMSS/test/tmss_test_environment_unittest_setup.py b/SAS/TMSS/test/tmss_test_environment_unittest_setup.py
index dc4f72644bf2b40058a6eb6571218f7cf6fd3d89..04b9882454838c474e06523d8017ebc9320aca07 100644
--- a/SAS/TMSS/test/tmss_test_environment_unittest_setup.py
+++ b/SAS/TMSS/test/tmss_test_environment_unittest_setup.py
@@ -134,7 +134,7 @@ def GET_and_assert_equal_expected_code(test_instance, url, expected_code):
     return r_dict
 
 
-def GET_and_assert_in_expected_response_result_list(test_instance, url, expected_content, expected_nbr_results):
+def GET_and_assert_in_expected_response_result_list(test_instance, url, expected_content, expected_nbr_results, expected_id=None):
     """
     GET from url and assert the expected code is returned and the expected content is in the response content
     Use this check when multiple results (list) are returned
@@ -159,7 +159,17 @@ def GET_and_assert_in_expected_response_result_list(test_instance, url, expected
             test_instance.assertIn(key, item.keys())
 
     if url_check:
-        assertDataWithUrls(test_instance, r_dict['results'][expected_nbr_results-1], expected_content)
+        # Find the expected id in result list if parameter is given (of curse for just one it does not make sense)
+        # There was an 'old' assumption that the last one should taken, but that is not reliable
+        if expected_id is not None:
+            for idx in range(0, expected_nbr_results):
+                if r_dict['results'][idx]['id'] == expected_id:
+                    expected_idx = idx
+                    break
+        else:
+            # this is the 'old' assumption that last object added will also be the last one in the result dict
+            expected_idx = expected_nbr_results-1
+        assertDataWithUrls(test_instance, r_dict['results'][expected_idx], expected_content)
     return r_dict