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/settings.py b/SAS/TMSS/src/tmss/settings.py
index 26e8d2c27b480115014a9165d1feb824649a5885..a4dc3da81e336cc45ff82c57369e19ec93a81c28 100644
--- a/SAS/TMSS/src/tmss/settings.py
+++ b/SAS/TMSS/src/tmss/settings.py
@@ -96,8 +96,16 @@ INSTALLED_APPS = [
     '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',
@@ -109,6 +117,7 @@ MIDDLEWARE = [
     'django.middleware.clickjacking.XFrameOptionsMiddleware'
 ]
 
+
 ROOT_URLCONF = 'lofar.sas.tmss.tmss.urls'
 
 TEMPLATES = [
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
index 9d8a1d48c82d0a81a12397985dab0c03097f0e8e..f250406f6e35263a43b47a21dbb436fe385815b0 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py
@@ -150,7 +150,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
 
     @property
     def duration(self) -> timedelta:
@@ -218,7 +218,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, ))
@@ -231,12 +231,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()
 
@@ -246,7 +246,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 f2dd9b2895028d88215609b534914d0ab644f076..cf8e50a3a664f8ac70aadb44a2d4748594727443 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
@@ -1,8 +1,6 @@
 """
 This file contains the database models
 """
-import logging
-logger = logging.getLogger(__name__)
 
 import logging
 logger = logging.getLogger(__name__)
@@ -21,6 +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
 
 from lofar.messaging.messagebus import ToBus, DEFAULT_BROKER, DEFAULT_BUSNAME
 from lofar.messaging.messages import EventMessage
@@ -48,7 +47,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
@@ -418,7 +417,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
@@ -560,19 +559,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
         '''
@@ -582,7 +581,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
         '''
@@ -616,7 +615,7 @@ class SchedulingUnitBlueprint(NamedCommon):
                             msg.subject, tobus.exchange, address[0], address[1], single_line_with_single_spaces(msg.content))
                 tobus.send(msg)
 
-    @property
+    @cached_property
     def duration(self) -> datetime.timedelta:
         '''return the overall duration of all tasks of this scheduling unit
         '''
@@ -625,7 +624,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
         '''
@@ -635,7 +634,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
         '''
@@ -645,7 +644,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
         '''
@@ -655,7 +654,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
         '''
@@ -777,7 +776,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()
@@ -787,7 +786,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()
@@ -797,26 +796,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_scheduling_relation.all()) + list(self.second_scheduling_relation.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
@@ -824,7 +823,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
         '''
@@ -844,7 +843,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?
@@ -855,7 +854,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?
@@ -867,7 +866,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?
@@ -891,7 +890,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()
@@ -901,7 +900,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()
@@ -911,7 +910,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
         '''
@@ -920,7 +919,7 @@ class TaskBlueprint(NamedCommon):
         else:
             return self.stop_time - self.start_time
 
-    @property
+    @cached_property
     def relative_start_time(self) -> datetime.timedelta:
         '''The relative start time is relative to the start time of the 'start' of the parent scheduling unit.
         It's based on the scheduling_relation's from the scheduling_unit's specification,
@@ -928,14 +927,14 @@ class TaskBlueprint(NamedCommon):
         '''
         scheduling_relations = list(self.first_scheduling_relation.all()) + list(self.second_scheduling_relation.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
@@ -943,7 +942,7 @@ class TaskBlueprint(NamedCommon):
                         return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset)
         return datetime.timedelta(seconds=0)
 
-    @property
+    @cached_property
     def relative_stop_time(self) -> datetime.timedelta:
         '''The relative_stop_time is the relative_start_time+duration.
         See relative_start_time for an explanation of it's intended usage.
@@ -956,7 +955,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
         '''
@@ -966,7 +965,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 00a1018c32c8fa4ccd3724329cf7e04bfea527b0..9d19d0c77460a5e99e5c2eb377f38b4adabbb0cb 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")
 
             tmss_projects = models.Project.objects.filter(name__startswith="TMSS-Commissioning").order_by('priority_rank').all()
             for project_nr, tmss_project in enumerate(tmss_projects):
@@ -69,18 +75,18 @@ def populate_test_data():
                     logger.info('created test scheduling_set: %s', scheduling_set.name)
 
                     for unit_nr in range(2 if 'normal' in project_prio_name else 1):
-                        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
                         scheduling_unit_spec = add_defaults_to_json_object_for_schema(strategy_template.template, strategy_template.scheduling_unit_template.schema)
 
                         # add the scheduling_unit_doc to a new SchedulingUnitDraft instance, and were ready to use it!
-                        scheduling_unit_draft = models.SchedulingUnitDraft.objects.create(name="UC1 unit p%s.s%s.u%s (%s)" % (project_nr+1, set_nr+1, unit_nr+1, project_prio_name),
+                        scheduling_unit_draft = models.SchedulingUnitDraft.objects.create(name="UC1 test scheduling unit %s.%s" % (set_nr+1, unit_nr+1),
                                                                                           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/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 71b73ffd50ac52e00f9ccd7dee1311009d23c679..a82aae88bdd1ba43eec58561450f1d4c0aa2a0a7 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/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 af3411b868953413e5b87113d6beade2c26c6ff5..0776702860d8c041eb193e83f2ba72b78d1d8031 100644
--- a/SAS/TMSS/src/tmss/urls.py
+++ b/SAS/TMSS/src/tmss/urls.py
@@ -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/test/test_utils.py b/SAS/TMSS/test/test_utils.py
index 627713b67e2e8ae4cea367e680a5dd39f945defc..a07119df5a674abe88bf85414186e8b79b2c7227 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):
         '''