diff --git a/SAS/TMSS/frontend/tmss_webapp/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json
index 8230388bc6f2c1f16805cf9a25ba4f05f6b6b000..705a5183e8801882a780f93350b30333e5bd3582 100644
--- a/SAS/TMSS/frontend/tmss_webapp/package.json
+++ b/SAS/TMSS/frontend/tmss_webapp/package.json
@@ -37,6 +37,7 @@
     "react-calendar-timeline": "^0.27.0",
     "react-dom": "^16.13.1",
     "react-frame-component": "^4.1.2",
+    "react-json-to-table": "^0.1.7",
     "react-json-view": "^1.19.1",
     "react-loader-spinner": "^3.1.14",
     "react-router-dom": "^5.2.0",
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js b/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js
index e9c0b245f5eaeeaf5bd579a4c42c6b67e46eae44..1a4f0d4290cde1e5be6ce84333dad9622678693a 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js
@@ -173,15 +173,15 @@ function Jeditor(props) {
         }
         editor = new JSONEditor(element, editorOptions);
         // editor.getEditor('root').disable();
-        if (props.disabled) {
-            editor.on('ready',() => {
-                editor.disable();
-            });
-        }
-        if (props.parentFunction) {
-            props.parentFunction(editorFunction);
-        }
         editorRef.current = editor;
+        editor.on('ready',() => {
+            if (props.disabled) {
+                editor.disable();
+            }
+            if (props.parentFunction) {
+                props.parentFunction(editorFunction);
+            }
+        });
         editor.on('change', () => {setEditorOutput()});
     };
 
@@ -208,8 +208,13 @@ function Jeditor(props) {
     /**
      * Function called by the parent component to perform certain action ib JEditor
      */
-    function editorFunction() {
-        editorRef.current.destroy();
+    function editorFunction(name, params) {
+        if (name === "setValue") {
+            const newValue = updateInput(_.cloneDeep(params[0]));
+            editorRef.current.setValue(newValue);
+        }   else {
+            editorRef.current.destroy();
+        }
     }
 
     /**
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js
index d611b7857cf365ed32c8eee56b0936e8eb7e5b87..180744f4df30abb3edb16851ca9db17313ef356d 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js
@@ -157,6 +157,12 @@ export class CalendarTimeline extends Component {
         setInterval(function(){setCurrentUTC(true)}, 60000);
         // Update UTC clock every second to keep the clock display live
         setInterval(function(){setCurrentUTC()}, 1000);
+        if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW) {
+            this.addWeekSunTimes(this.state.defaultStartTime, this.state.defaultEndTime, this.state.group, this.state.items)
+                .then(items => {
+                    this.setState({items: items});
+                });
+        }
     }
 
     shouldComponentUpdate() {
@@ -251,22 +257,21 @@ export class CalendarTimeline extends Component {
         if (startMonth !== endMonth) {
             monthDuration = `(${startMonth}-${endMonth})`;
         }
-        return (<div {...getRootProps()} 
-                    style={{color: '#ffffff', textAlign: "right", width: `${this.state.sidebarWidth}px`, 
-                            paddingRight: '10px', backgroundColor: '#8ba7d9'}}>
-                    <div style={{height:'30px'}}>{this.state.viewType===UIConstants.timeline.types.NORMAL?
+        return (<div {...getRootProps()} className="sidebar-header"
+                    style={{width: `${this.state.sidebarWidth}px`}}>
+                    <div className="sidebar-header-row">{this.state.viewType===UIConstants.timeline.types.NORMAL?
                                     (this.state.dayHeaderVisible?`Day${monthDuration}`:`Week${monthDuration}`)
                                     :`Week (${this.state.timelineStartDate.week()}) / Day`}</div> 
-                    <div style={{height:'30px'}}>{this.state.dayHeaderVisible?`UTC(Hr)`:`UTC(Day)`}</div>
-                    <div style={{height:'30px'}}>{this.state.dayHeaderVisible?`LST(Hr)`:`LST(Day)`}</div>
-                    {this.state.viewType === UIConstants.timeline.types.NORMAL && 
-                        <div className="p-grid" 
-                            style={{height:this.props.showSunTimings?'30px':'0px', paddingTop:'10px', paddingLeft:'10px'}}>
-                            <div className="col-4" style={{marginTop:'2px', paddingLeft:'5px', backgroundColor:'yellow', color: '#212529'}}>Sunrise</div>
-                            <div className="col-4" style={{marginTop:'2px', paddingLeft:'5px', backgroundColor:'orange', color: '#212529'}}>Sunset</div>
-                            <div className="col-4" style={{marginTop:'2px', paddingLeft:'5px', backgroundColor:'blue'}}>Night</div>
+                    <div className="sidebar-header-row">{this.state.dayHeaderVisible?`UTC(Hr)`:`UTC(Day)`}</div>
+                    <div className="sidebar-header-row">{this.state.dayHeaderVisible?`LST(Hr)`:`LST(Day)`}</div>
+                    {/* {this.state.viewType === UIConstants.timeline.types.NORMAL &&  */}
+                        <div className="p-grid legend-row" 
+                            style={{height:this.props.showSunTimings?'30px':'0px'}}>
+                            <div className="col-4 legend-suntime legend-sunrise">Sunrise</div>
+                            <div className="col-4 legend-suntime legend-sunset">Sunset</div>
+                            <div className="col-4 legend-suntime legend-night">Night</div>
                         </div>
-                    }
+                    {/* } */}
                 </div>
         );
     }
@@ -449,8 +454,8 @@ export class CalendarTimeline extends Component {
             }
             const nightStyle = {
                 lineHeight: '30px',
-                backgroundColor: 'blue',
-                color: 'blue'
+                backgroundColor: 'grey',
+                color: 'grey'
             }
             const sunriseStyle = {
                 lineHeight: '30px',
@@ -463,27 +468,25 @@ export class CalendarTimeline extends Component {
                 color: 'orange'
             }
             // Get the intervals UTC date format and time
-            const intervalDate = interval.startTime.clone().utc().format("YYYYMMDDT12:00:00");
+            const intervalDate = interval.startTime.clone().utc().format("YYYY-MM-DD");
             const intervalTime = interval.startTime.clone().utc();
             // Get the suntime for the UTC date
             const intervalDateSunTime = sunTimeMap[intervalDate];
             let intervalStyle = dayStyle;
             // If suntime is available display suntime blocks
             if (intervalDateSunTime) {
-                // Set 15 minutes duration for sunrise and sunset and create blocks accordingly
-                if (intervalTime.isBefore(intervalDateSunTime.sunrise) || 
-                    intervalTime.isAfter(intervalDateSunTime.sunset.clone().add(14, 'minutes'))) {
-                        intervalStyle = nightStyle;
-                }   else if (intervalTime.isSame(intervalDateSunTime.sunrise) ||
-                                intervalTime.isBefore(intervalDateSunTime.sunrise.clone().add(15, 'minutes'))) {
+                if (intervalTime.isBefore(intervalDateSunTime.sunrise.start) || 
+                    intervalTime.isAfter(intervalDateSunTime.sunset.end)) {
+                    intervalStyle = nightStyle;
+                }   else if (intervalTime.isSameOrAfter(intervalDateSunTime.sunrise.start) &&
+                            intervalTime.isSameOrBefore(intervalDateSunTime.sunrise.end)) {
                     intervalStyle = sunriseStyle;
-                }   else if (intervalTime.isSame(intervalDateSunTime.sunset) || 
-                                (intervalTime.isAfter(intervalDateSunTime.sunset) &&
-                                intervalTime.isBefore(intervalDateSunTime.sunset.clone().add(15, 'minutes')))) {
+                }   else if (intervalTime.isSameOrAfter(intervalDateSunTime.sunset.start) && 
+                            intervalTime.isSameOrBefore(intervalDateSunTime.sunset.end)) {
                     intervalStyle = sunsetStyle;
                 }
                 return (
-                    <div
+                    <div clasName={`suntime-header, ${intervalStyle}`}
                     {...getIntervalProps({
                         interval,
                         style: intervalStyle
@@ -500,46 +503,133 @@ export class CalendarTimeline extends Component {
     }
 
     /**
-     * Function to render sunrise timings on the timeline view in normal view.
+     * Function to render sunrise and before sunrise timings on the timeline view in normal view.
      * @param {Array} sunRiseTimings 
      */
     renderSunriseMarkers(sunRiseTimings) {
+        let endPoint = 0;
         return (
             <>
             {sunRiseTimings && sunRiseTimings.length>0 && sunRiseTimings.map((item, index) => (
-            <CustomMarker key={"sunrise-"+index} date={item}>
-                {({ styles, date }) => {
-                    const customStyles = {
-                    ...styles,
-                    backgroundColor: 'yellow',
-                    width: '3px'
-                    }
-                    return <div style={customStyles} />
-                }}
-            </CustomMarker>
+            <>
+                {/* Marker to get the position of the sunrise end time */}
+                <CustomMarker key={"sunrise-"+index} date={item.end}>
+                    {({ styles, date }) => {
+                        endPoint = styles.left;
+                        return ""
+                    }}
+                </CustomMarker>
+                {/* Marker to represent dark light before sunrise on the day */}
+                <CustomMarker key={"sunrise-"+index} date={item.start.clone().hours(0).minutes(0).seconds(0)}>
+                    {({ styles, date }) => {
+                        const customStyles = {
+                            ...styles,
+                            backgroundColor: 'grey',
+                            opacity:0.7,
+                            zIndex: 10,
+                            // width: '3px'
+                            width: (endPoint-styles.left)
+                        }
+                        return <div style={customStyles} />
+                    }}
+                </CustomMarker>
+                {/* Marker to represent the duration of sunrise */}
+                <CustomMarker key={"sunrise-"+index} date={item.start}>
+                    {({ styles, date }) => {
+                        const customStyles = {
+                        ...styles,
+                        backgroundColor: 'yellow',
+                        opacity:0.7,
+                        zIndex: 10,
+                        // width: '3px'
+                        width: (endPoint-styles.left)
+                        }
+                        return <div style={customStyles} />
+                    }}
+                </CustomMarker>
+            </>
             ))}
             </>
         );
     }
 
     /**
-     * Function to render sunrise timings on the timeline view in normal view.
+     * Function to render sunset & after sunset timings on the timeline view in normal view.
      * @param {Array} sunSetTimings 
      */
     renderSunsetMarkers(sunSetTimings) {
+        let endPoint = 0;
         return (
             <>
             {sunSetTimings && sunSetTimings.length>0 && sunSetTimings.map((item, index) => (
-            <CustomMarker key={"sunset-"+index} date={item}>
-                {({ styles, date }) => {
-                    const customStyles = {
-                    ...styles,
-                    backgroundColor: 'orange',
-                    width: '3px'
-                    }
-                    return <div style={customStyles} />
-                }}
-            </CustomMarker>
+            <>
+                {/* Marker to get the position of the sunset end time */}
+                <CustomMarker key={"sunset-"+index} date={item.end}>
+                        {({ styles, date }) => {
+                            endPoint = styles.left;
+                            return ""
+                        }}
+                </CustomMarker>
+                {/* Marker to represent the dark light after sunset */}
+                <CustomMarker key={"sunset-"+index} date={item.start.clone().hours(23).minutes(59).seconds(59)}>
+                    {({ styles, date }) => {
+                        const customStyles = {
+                        ...styles,
+                        backgroundColor: 'grey',
+                        opacity:0.7,
+                        zIndex: 10,
+                        left: endPoint,
+                        width: styles.left-endPoint
+                        }
+                        return <div style={customStyles} />
+                    }}
+                </CustomMarker>
+                {/* Marker to represent the actual sunset duration */}
+                <CustomMarker key={"sunset-"+index} date={item.start}>
+                    {({ styles, date }) => {
+                        const customStyles = {
+                        ...styles,
+                        backgroundColor: 'orange',
+                        opacity:0.7,
+                        zIndex: 10,
+                        width: endPoint - styles.left
+                        }
+                        return <div style={customStyles} />
+                    }}
+                </CustomMarker>
+            </>
+            ))}
+            </>
+        );
+    }
+
+    /**
+     * Function to render sunrise timings on the timeline view in normal view.
+     * @param {Array} sunSetTimings 
+     */
+    renderNightMarkers(sunRiseTimings, sunSetTimings) {
+        let endPoint = 0;
+        return (
+            <>
+            {sunSetTimings && sunSetTimings.length>0 && sunSetTimings.map((item, index) => (
+            <>
+                <CustomMarker key={"sunset-"+index} date={item.end}>
+                        {({ styles, date }) => {
+                            endPoint = styles.left;
+                            return ""
+                        }}
+                </CustomMarker>
+                <CustomMarker key={"sunset-"+index} date={item.start}>
+                    {({ styles, date }) => {
+                        const customStyles = {
+                        ...styles,
+                        backgroundColor: 'orange',
+                        width: endPoint - styles.left
+                        }
+                        return <div style={customStyles} />
+                    }}
+                </CustomMarker>
+            </>
             ))}
             </>
         );
@@ -577,10 +667,16 @@ export class CalendarTimeline extends Component {
 
     /** Custom function to pass to timeline component to render item */
     renderItem({ item, timelineContext, itemContext, getItemProps, getResizeProps }) {
+        /* Reduce the item height so that the suntimings can be viewed above the item block.
+           Also suntimes are rendered as items with tiny height to represent as horizontal bar above the actual items */
+        if (item.type === "SUNTIME") {
+            itemContext.dimensions.height = 3;
+        }   else {
+            itemContext.dimensions.height -= 3;
+            itemContext.dimensions.top += 3;
+        }
         const { left: leftResizeProps, right: rightResizeProps } = getResizeProps();
         const backgroundColor = itemContext.selected?item.bgColor:item.bgColor;
-        // const backgroundColor = itemContext.selected ? (itemContext.dragging ? "red" : item.selectedBgColor) : item.bgColor;
-        // const borderColor = itemContext.resizing ? "red" : item.color;
         let itemContentStyle = {lineHeight: `${Math.floor(itemContext.dimensions.height)}px`, 
                                   fontSize: "14px",
                                   overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
@@ -603,7 +699,8 @@ export class CalendarTimeline extends Component {
                 borderRadius: 3,
                 borderLeftWidth: itemContext.selected ? 3 : 1,
                 borderRightWidth: itemContext.selected ? 3 : 1,
-                opacity: item.type==="SUNTIME"?0.6:1
+                // opacity: item.type==="SUNTIME"?0.6:1
+                zIndex: item.type==="SUNTIME"?79:80
               },
               onMouseDown: () => {
                   if (item.type !== "SUNTIME") {
@@ -623,7 +720,7 @@ export class CalendarTimeline extends Component {
                 //whiteSpace: "nowrap"
               }}
             >
-              { this.state.viewType===UIConstants.timeline.types.WEEKVIEW &&
+              { this.state.viewType===UIConstants.timeline.types.WEEKVIEW && item.type !== "SUNTIME" &&
                 <><div style={itemContentStyle}><i style={{fontSize:"12px"}} className="fa fa-user" title="Friend"></i><span>{item.project}</span></div>
                     <div style={itemContentStyle}><span>{item.duration}</span></div>
                     <div style={itemContentStyle}><span>{item.band}</span></div> </>}
@@ -719,14 +816,18 @@ export class CalendarTimeline extends Component {
         const noOfDays = endTime.diff(startTime, 'days');
         for (const number of _.range(noOfDays+1)) {
             const date = startTime.clone().add(number, 'days').hours(12).minutes(0).seconds(0);
-            const formattedDate = date.format("YYYYMMDDTHH:mm:ss");
-            UtilService.getSunTimings(formattedDate+"Z").then(timings => {
-                const sunriseTime = moment.utc(timings.sun_rise.split('.')[0]);
-                const sunsetTime = moment.utc(timings.sun_set.split('.')[0]);
-                if (moment.utc(timings.sun_rise).isAfter(startTime)) {
+            const formattedDate = date.format("YYYY-MM-DD");
+            UtilService.getSunTimings(formattedDate).then(timings => {
+                const sunriseStartTime = moment.utc(timings.sun_rise.start.split('.')[0]);
+                const sunriseEndTime = moment.utc(timings.sun_rise.end.split('.')[0]);
+                const sunsetStartTime = moment.utc(timings.sun_set.start.split('.')[0]);
+                const sunsetEndTime = moment.utc(timings.sun_set.end.split('.')[0]);
+                const sunriseTime = {start: sunriseStartTime, end: sunriseEndTime};
+                const sunsetTime = {start: sunsetStartTime, end: sunsetEndTime};
+                if (moment.utc(timings.sunriseEndTime).isAfter(startTime)) {
                     sunRiseTimings.push(sunriseTime);
                 }
-                if (moment.utc(timings.sun_set).isBefore(endTime)) {
+                if (moment.utc(timings.sunsetStartTime).isBefore(endTime)) {
                     sunSetTimings.push(sunsetTime);
                 }
                 sunTimeMap[formattedDate] = {sunrise: sunriseTime, sunset: sunsetTime};
@@ -735,33 +836,62 @@ export class CalendarTimeline extends Component {
         }
     }
 
+    /**
+     * 
+     * @param {moment} startTime 
+     * @param {moment} endTime 
+     * @param {Array} stationGroup - Array of station group objects
+     * @param {Array} items - Array of Item objects
+     */
     async addStationSunTimes(startTime, endTime, stationGroup, items) {
         const noOfDays = endTime.diff(startTime, 'days');
         let sunItems = _.cloneDeep(items);
         for (const number of _.range(noOfDays+1)) {
             for (const station of stationGroup) {
                 const date = startTime.clone().add(number, 'days').hours(12).minutes(0).seconds(0);
-                const timings = await UtilService.getSunTimings(date.format("YYYYMMDDTHH:mm:ss")+"Z", station.id);
-                let sunriseItem = { id: `sunrise-${number}-${station.id}`, 
-                                    group: station.id,
-                                    title: timings.sun_rise,
-                                    project: "",
-                                    name: "",
-                                    duration: "",
-                                    start_time: moment.utc(timings.sun_rise),
-                                    end_time: moment.utc(timings.sun_rise).add(5, 'minutes'),
-                                    bgColor: "yellow",
-                                    selectedBgColor: "yellow",
-                                    type: "SUNTIME"};
-                sunItems.push(sunriseItem);
-                let sunsetItem = _.cloneDeep(sunriseItem);
-                sunsetItem.id = `sunset-${number}-${station.id}`;
-                sunsetItem.start_time = moment.utc(timings.sun_set);
-                sunsetItem.end_time = moment.utc(timings.sun_set).add(5, 'minutes');
-                sunsetItem.bgColor = "orange";
-                sunsetItem.selectedBgColor = "0range";
-                sunItems.push(sunsetItem);
-                
+                const timings = await UtilService.getSunTimings(date.format("YYYY-MM-DD"), station.id);
+                if (timings) {
+                    let sunriseItem = { id: `sunrise-${number}-${station.id}`, 
+                                        group: station.id,
+                                        // title: `${timings.sun_rise.start} to ${timings.sun_rise.end}`,
+                                        title: "",
+                                        project: "",
+                                        name: "",
+                                        duration: "",
+                                        start_time: moment.utc(timings.sun_rise.start),
+                                        end_time: moment.utc(timings.sun_rise.end),
+                                        bgColor: "yellow",
+                                        selectedBgColor: "yellow",
+                                        type: "SUNTIME"};
+                    sunItems.push(sunriseItem);
+                    let sunsetItem = _.cloneDeep(sunriseItem);
+                    sunsetItem.id = `sunset-${number}-${station.id}`;
+                    // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`;
+                    sunsetItem.title = "";
+                    sunsetItem.start_time = moment.utc(timings.sun_set.start);
+                    sunsetItem.end_time = moment.utc(timings.sun_set.end);
+                    sunsetItem.bgColor = "orange";
+                    sunsetItem.selectedBgColor = "orange";
+                    sunItems.push(sunsetItem);
+                    let befSunriseItem = _.cloneDeep(sunriseItem);
+                    befSunriseItem.id = `bef-sunrise-${number}-${station.id}`;
+                    // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`;
+                    befSunriseItem.title = "";
+                    befSunriseItem.start_time = moment.utc(timings.sun_rise.start).hours(0).minutes(0).seconds(0);
+                    befSunriseItem.end_time = moment.utc(timings.sun_rise.start);
+                    befSunriseItem.bgColor = "grey";
+                    befSunriseItem.selectedBgColor = "grey";
+                    sunItems.push(befSunriseItem);
+                    let afterSunsetItem = _.cloneDeep(sunriseItem);
+                    afterSunsetItem.id = `aft-sunset-${number}-${station.id}`;
+                    // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`;
+                    afterSunsetItem.title = "";
+                    afterSunsetItem.start_time = moment.utc(timings.sun_set.end);
+                    afterSunsetItem.end_time = moment.utc(timings.sun_set.end).hours(23).minutes(59).seconds(59);
+                    afterSunsetItem.bgColor = "grey";
+                    afterSunsetItem.selectedBgColor = "grey";
+                    sunItems.push(afterSunsetItem);
+                }
             }
         }
         if (!this.props.showSunTimings && this.state.viewType === UIConstants.timeline.types.NORMAL) {
@@ -770,6 +900,73 @@ export class CalendarTimeline extends Component {
         return items;
     }
 
+    /**
+     * To Render sunrise, sunset and night times as horizontal bar, new items are created and appended with actual items.
+     * @param {moment} startTime 
+     * @param {moment} endTime 
+     * @param {Array} weekGroup 
+     * @param {Array} items 
+     */
+    async addWeekSunTimes(startTime, endTime, weekGroup, items) {
+        const noOfDays = endTime.diff(startTime, 'days');
+        let sunItems = _.cloneDeep(items);
+        for (const weekDay of weekGroup) {
+            if (weekDay.value) {
+                const timings = await UtilService.getSunTimings(weekDay.value.format("YYYY-MM-DD"), 'CS001');
+                const sunriseStart = moment.utc(timings.sun_rise.start);
+                const sunriseEnd = moment.utc(timings.sun_rise.end);
+                const sunsetStart = moment.utc(timings.sun_set.start);
+                const sunsetEnd = moment.utc(timings.sun_set.end);
+                if (timings) {
+                    let sunriseItem = { id: `sunrise-${weekDay.id}`, 
+                                        group: weekDay.id,
+                                        // title: `${timings.sun_rise.start} to ${timings.sun_rise.end}`,
+                                        title: "",
+                                        project: "",
+                                        name: "",
+                                        duration: "",
+                                        start_time: startTime.clone().hours(sunriseStart.hours()).minutes(sunriseStart.minutes()).seconds(sunriseStart.seconds()),
+                                        end_time: startTime.clone().hours(sunriseEnd.hours()).minutes(sunriseEnd.minutes()).seconds(sunriseEnd.seconds()),
+                                        bgColor: "yellow",
+                                        selectedBgColor: "yellow",
+                                        type: "SUNTIME"};
+                    sunItems.push(sunriseItem);
+                    let sunsetItem = _.cloneDeep(sunriseItem);
+                    sunsetItem.id = `sunset-${weekDay.id}`;
+                    // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`;
+                    sunsetItem.title = "";
+                    sunsetItem.start_time = startTime.clone().hours(sunsetStart.hours()).minutes(sunsetStart.minutes()).seconds(sunsetStart.seconds());
+                    sunsetItem.end_time = startTime.clone().hours(sunsetEnd.hours()).minutes(sunsetEnd.minutes()).seconds(sunsetEnd.seconds());
+                    sunsetItem.bgColor = "orange";
+                    sunsetItem.selectedBgColor = "orange";
+                    sunItems.push(sunsetItem);
+                    let befSunriseItem = _.cloneDeep(sunriseItem);
+                    befSunriseItem.id = `bef-sunrise-${weekDay.id}`;
+                    // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`;
+                    befSunriseItem.title = "";
+                    befSunriseItem.start_time = startTime.clone().hours(0).minutes(0).seconds(0);
+                    befSunriseItem.end_time = startTime.clone().hours(sunriseStart.hours()).minutes(sunriseStart.minutes()).seconds(sunriseStart.seconds());;
+                    befSunriseItem.bgColor = "grey";
+                    befSunriseItem.selectedBgColor = "grey";
+                    sunItems.push(befSunriseItem);
+                    let afterSunsetItem = _.cloneDeep(sunriseItem);
+                    afterSunsetItem.id = `aft-sunset-${weekDay.id}`;
+                    // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`;
+                    afterSunsetItem.title = "";
+                    afterSunsetItem.start_time = startTime.clone().hours(sunsetEnd.hours()).minutes(sunsetEnd.minutes()).seconds(sunsetEnd.seconds());
+                    afterSunsetItem.end_time = startTime.clone().hours(23).minutes(59).seconds(59);
+                    afterSunsetItem.bgColor = "grey";
+                    afterSunsetItem.selectedBgColor = "grey";
+                    sunItems.push(afterSunsetItem);
+                }
+            }
+        }
+        if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW) {
+            items = sunItems;
+        }
+        return items;
+    }
+
     /**
      * Resets the timeline view to default zoom and move to the current timeline
      */
@@ -964,11 +1161,13 @@ export class CalendarTimeline extends Component {
         dayHeaderVisible = rangeDays > 35?false: true; 
         weekHeaderVisible = rangeDays > 35?true: false; 
         lstDateHeaderUnit = rangeDays > 35?"day":"hour";
+        const items = await this.addWeekSunTimes(timelineStart, timelineEnd, group, result.items);
+        console.log(items);
         this.setState({defaultStartTime: timelineStart, defaultEndTime: timelineEnd,
                         timelineStartDate: timelineStart, timelineEndDate: timelineEnd,
                         zoomLevel: this.ZOOM_LEVELS[this.ZOOM_LEVELS.length-1].name, isTimelineZoom: false,
                         dayHeaderVisible: dayHeaderVisible, weekHeaderVisible: weekHeaderVisible,
-                        lstDateHeaderUnit: lstDateHeaderUnit, group: group, items: result.items});
+                        lstDateHeaderUnit: lstDateHeaderUnit, group: group, items: items});
         this.loadLSTDateHeaderMap(startDate, endDate, lstDateHeaderUnit);
         this.setState({isWeekLoading: false});
     }
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss
index c2fb7b46ab00ed3721db70e61b834f5c9d17b412..76b02736fb1959096ef3067f75b67f2ad1a72336 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss
+++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss
@@ -1,5 +1,5 @@
 .constraints-summary>div {
-    overflow: scroll;
+    overflow-y: scroll;
     max-height: 500px;
     margin-bottom: 10px;
 }
@@ -24,4 +24,25 @@
 
 .task-summary #block_container {
     margin-top: 0px;
-}
\ No newline at end of file
+}
+
+/*
+ *  STYLE 3
+ */
+
+ .json-to-table::-webkit-scrollbar-track
+ {
+     -webkit-box-shadow: inset 0 0 6px rgba(248, 245, 245, 0.3);
+     background-color: #F5F5F5;
+ }
+ 
+ .json-to-table::-webkit-scrollbar
+ {
+     width: 6px;
+     background-color: #F5F5F5;
+ }
+ 
+ .json-to-table::-webkit-scrollbar-thumb
+ {
+     background-color: #0000007c;
+ }
\ No newline at end of file
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss
index 25e0ca50ba4e4546260f615e0ccb3150dc01d50a..51b2e71b3a46bf57fa21eb4e54b3de8aaa0152a1 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss
+++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss
@@ -1,7 +1,7 @@
 .sticky {
     position: sticky;
     top:49px;
-    z-index:1000;
+    z-index:999;
 }
 
 .rct-sidebar-row {
@@ -84,6 +84,66 @@
     margin-left: 10px;
 }
 
+.sidebar-header {
+    color: #ffffff;
+    text-align: right;
+    padding-right: 10px;
+    background-color: #8ba7d9;
+}
+
+.sidebar-header-row {
+    height: 30px;
+}
+
+.legend-row {
+    padding-top: 10px;
+    padding-left: 10px;
+    font-size: 14px;
+}
+
+.legend-suntime {
+    margin-top: 2px;
+    padding-left: 3px !important;
+}
+
+.legend-sunrise {
+    background-color: yellow;
+    color: #212529;
+}
+
+.legend-sunset {
+    background-color: orange;
+    color: #212529;
+}
+
+.legend-night {
+    background-color: grey;
+}
+
+.suntime-header {
+    line-height: 30px;
+}
+
+.suntime-header-day {
+    background-color: white;
+    color: white;
+}
+
+.suntime-header-night {
+    background-color: grey;
+    color: grey;
+}
+
+.suntime-header-sunrise {
+    background-color: yellow;
+    color: yellow;
+}
+
+.suntime-header-sunset {
+    background-color: orange;
+    color: orange;
+}
+
 .resize-div,
 .resize-div-min,
 .resize-div-avg,
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js
index 5de435d85b1f8e16a09f7e43c85ffaffd3da6227..1bcbcefcbd4a7cd6a5bbb2c2cf96eb7c4b7c2800 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js
@@ -1,5 +1,6 @@
 import React, { useState, useEffect } from 'react';
 import moment from 'moment';
+import _ from 'lodash';
 import Jeditor from '../../components/JSONEditor/JEditor'; 
 import UnitConversion from '../../utils/unit.converter';
 /* eslint-disable react-hooks/exhaustive-deps */
@@ -138,7 +139,7 @@ export default (props) => {
     };
   
     const modifyInitiValue = () => {
-        const initValue = { ...props.initValue }
+        const initValue = _.cloneDeep(props.initValue);
         // For DateTime
         for (let key in initValue.time) {
             if (typeof initValue.time[key] === 'string') {
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js
index f70b3eb5b38d6797bc9b578dd2e660c9a8379b54..4dba59e7cc582d9ff47663d3130e2dd9efdea3ab 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js
@@ -396,7 +396,6 @@ export class SchedulingUnitCreate extends Component {
              },
             projectDisabled: (this.props.match.params.project? true:false),
             observStrategy: {},
-            selectedStations:{},
             paramsOutput: null,
             validEditor: false,
             validFields: {},
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js
index 7263fc7b3441386d301f4bba6e5e1e82aedd368c..4b2ef70081a130dd75f9567e8c1ec2616b186c90 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom/cjs/react-router-dom.min';
 import moment from 'moment';
 import _ from 'lodash';
 import ViewTable from '../../components/ViewTable';
-import { JSONToHTMLTable } from '@kevincobain2000/json-to-html-table'
+import { JsonToTable } from "react-json-to-table";
 import SchedulingConstraints from './Scheduling.Constraints';
 
 /**
@@ -42,7 +42,7 @@ export class SchedulingUnitSummary extends Component {
             /* Format the object to remove empty values*/
             const constraint = this.getFormattedConstraint(constraintsDoc[constraintKey]);
             if (constraint) {
-                orderedConstraints[constraintKey] = constraint;
+                orderedConstraints[constraintKey.replace('_',' ')] = constraint;
             }
         }
         return orderedConstraints;
@@ -66,7 +66,7 @@ export class SchedulingUnitSummary extends Component {
                     break;
                 }
                 case "boolean": {
-                    constraint = constraint?constraint:null;
+                    constraint = constraint?'Yes':null;
                     break;
                 }
                 case "object": {
@@ -88,7 +88,7 @@ export class SchedulingUnitSummary extends Component {
                         for (const objectKey of _.keys(constraint)) {
                             let object = this.getFormattedConstraint(constraint[objectKey]);
                             if (object) {
-                                newObject[objectKey] = object;
+                                newObject[objectKey.replace(/_/g, ' ')] = object;
                             }
                         }
                         constraint = (!_.isEmpty(newObject))? newObject:null;
@@ -143,10 +143,12 @@ export class SchedulingUnitSummary extends Component {
                             </div>
                             {/* Scheduling Constraint Display in table format */}
                             {constraintsDoc &&
+                                <>
                                 <div className="col-12 constraints-summary">
                                     <label>Constraints:</label>
-                                    <JSONToHTMLTable data={constraintsDoc} tableClassName="table table-sm"/>
+                                    <JsonToTable json={constraintsDoc} />
                                 </div>
+                                </>
                             }
                         </>
                     }
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js
index 5ab65e6a7b09886f107dc78943236999c1836156..2fd2e272fc587b7ed335de24b48ed3a6c31a4352 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js
@@ -46,6 +46,7 @@ export class TimelineView extends Component {
             isSummaryLoading: false
         }
         this.STATUS_BEFORE_SCHEDULED = ['defining', 'defined', 'schedulable'];  // Statuses before scheduled to get station_group
+        this.allStationsGroup = [];
 
         this.onItemClick = this.onItemClick.bind(this);
         this.closeSUDets = this.closeSUDets.bind(this);
@@ -60,7 +61,8 @@ export class TimelineView extends Component {
                             ScheduleService.getSchedulingUnitBlueprint(),
                             ScheduleService.getSchedulingUnitDraft(),
                             ScheduleService.getSchedulingSets(),
-                            UtilService.getUTC()] ;
+                            UtilService.getUTC(),
+                            ScheduleService.getStations('All')] ;
         Promise.all(promises).then(async(responses) => {
             const projects = responses[0];
             const suBlueprints = _.sortBy(responses[1].data.results, 'name');
@@ -105,7 +107,9 @@ export class TimelineView extends Component {
                     }
                 }
             }
-
+            for (const station of responses[5]['stations']) {
+                this.allStationsGroup.push({id: station, title: station});
+            }
             this.setState({suBlueprints: suBlueprints, suDrafts: suDrafts, group: group, suSets: suSets,
                             projects: projects, suBlueprintList: suList, 
                             items: items, currentUTC: currentUTC, isLoading: false});
@@ -117,9 +121,6 @@ export class TimelineView extends Component {
      * @param {Object} suBlueprint 
      */
     getTimelineItem(suBlueprint) {
-        // Temporary for testing
-        const diffOfCurrAndStart = moment().diff(moment(suBlueprint.stop_time), 'seconds');
-        // suBlueprint.status = diffOfCurrAndStart>=0?"FINISHED":"DEFINED";
         let item = { id: suBlueprint.id, 
             group: suBlueprint.suDraft.id,
             title: `${suBlueprint.project} - ${suBlueprint.suDraft.name} - ${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`,
@@ -189,7 +190,7 @@ export class TimelineView extends Component {
                     if (this.state.stationView) {
                         const loadSubtasks = this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 ;
                         suBlueprint.tasks = await ScheduleService.getTaskBlueprintsBySchedulingUnit(suBlueprint, true, loadSubtasks);
-                        this.getStationItemGroups(suBlueprint, timelineItem, group, items);
+                        this.getStationItemGroups(suBlueprint, timelineItem, this.allStationsGroup, items);
                     }   else {
                         items.push(timelineItem);
                         if (!_.find(group, {'id': suBlueprint.suDraft.id})) {
@@ -207,7 +208,7 @@ export class TimelineView extends Component {
         this.setState({suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => {return suBlueprint.start_time!=null})});
         // On range change close the Details pane
         // this.closeSUDets();
-        return {group: _.sortBy(group,'id'), items: items};
+        return {group: this.stationView?this.allStationsGroup:_.sortBy(group,'id'), items: items};
     }
 
     /**
@@ -245,9 +246,9 @@ export class TimelineView extends Component {
             stationItem.id = `${stationItem.id}-${station}`;
             stationItem.group = station;
             items.push(stationItem);
-            if (!_.find(group, {'id': station})) {
-                group.push({'id': station, title: station});
-            }
+            // if (!_.find(group, {'id': station})) {
+            //     group.push({'id': station, title: station});
+            // }
         }
     }
 
@@ -281,7 +282,7 @@ export class TimelineView extends Component {
             const suBlueprint = _.find(suBlueprints, {actionpath: data.actionpath});
             let timelineItem = this.getTimelineItem(suBlueprint);
             if (this.state.stationView) {
-                this.getStationItemGroups(suBlueprint, timelineItem, group, items);
+                this.getStationItemGroups(suBlueprint, timelineItem, this.allStationsGroup, items);
             }   else {
                 items.push(timelineItem);
                 if (!_.find(group, {'id': suBlueprint.suDraft.id})) {
@@ -290,7 +291,7 @@ export class TimelineView extends Component {
             }
         }
         if (this.timeline) {
-            this.timeline.updateTimeline({group: _.sortBy(group,"id"), items: items});
+            this.timeline.updateTimeline({group: this.state.stationView?this.allStationsGroup:_.sortBy(group,"id"), items: items});
         }
     }
 
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js
index 5d629c58d0dbbc340bf084c0f57ff31f0868cee2..4b8d41ca2c1fd83d8476980d9ac4ef882a44af0c 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js
@@ -39,8 +39,10 @@ const UtilService = {
         console.error(error);
       }
     },
+    /** Function to fetch sun timings from the backend for single station. */
     getSunTimings: async(timestamp, station) => {
       try {
+        station = station?station:"CS001";
         let stationTimestamp = (station?`${station}-`:"") + timestamp;
         let localSunTimeMap = localStorage.getItem('SUN_TIME_MAP');
         if (localSunTimeMap) {
@@ -51,14 +53,9 @@ const UtilService = {
         } else {
           localSunTimeMap = {};
         }
-        // const url = `/api/sun_rise_and_set/${timestamp}`;
-        // const sunTimings = (await axios.get(url)).data;
-        let sunTimings = {sun_rise: moment.utc(moment(timestamp, "YYYYMMDDTHH:mm:ss")).format('YYYY-MM-DDT06:30:00.sssss')+"Z", 
-                            sun_set: moment.utc(moment(timestamp, "YYYYMMDDTHH:mm:ss")).format('YYYY-MM-DDT17:00:00.sssss')+"Z"};
-        if (station==="CS001") {
-          sunTimings = {sun_rise: moment.utc(moment(timestamp, "YYYYMMDDTHH:mm:ss")).format('YYYY-MM-DDT05:30:00.sssss')+"Z", 
-                            sun_set: moment.utc(moment(timestamp, "YYYYMMDDTHH:mm:ss")).format('YYYY-MM-DDT16:00:00.sssss')+"Z"};
-        }
+        const url = `/api/util/sun_rise_and_set?stations=${station?station:'CS001'}&timestamps=${timestamp}`;
+        const stationSunTimings = (await axios.get(url)).data;
+        let sunTimings = {sun_rise: stationSunTimings[station]['sunrise'][0], sun_set: stationSunTimings[station]['sunset'][0]};
         localSunTimeMap[stationTimestamp] = sunTimings;
         localStorage.setItem('SUN_TIME_MAP', JSON.stringify(localSunTimeMap));
         return sunTimings;
diff --git a/SAS/TMSS/services/scheduling/lib/constraints/template_constraints_v1.py b/SAS/TMSS/services/scheduling/lib/constraints/template_constraints_v1.py
index 247f89851ccdda58cdb07b98639c1349c45825fc..6eb1f5084d741164d127812a55da7729e379ad7b 100644
--- a/SAS/TMSS/services/scheduling/lib/constraints/template_constraints_v1.py
+++ b/SAS/TMSS/services/scheduling/lib/constraints/template_constraints_v1.py
@@ -31,7 +31,7 @@ from datetime import datetime, timedelta
 from dateutil import parser
 
 from lofar.sas.tmss.tmss.tmssapp import models
-from lofar.sas.tmss.tmss.tmssapp.conversions import create_astroplan_observer_for_station, Time, timestamps_and_stations_to_sun_rise_and_set
+from lofar.sas.tmss.tmss.tmssapp.conversions import create_astroplan_observer_for_station, Time, timestamps_and_stations_to_sun_rise_and_set, coordinates_and_timestamps_to_separation_from_bodies
 
 from . import ScoredSchedulingUnit
 
@@ -133,7 +133,21 @@ def can_run_within_timewindow_with_sky_constraints(scheduling_unit: models.Sched
     constraints = scheduling_unit.draft.scheduling_constraints_doc
     # TODO: TMSS-245 TMSS-250 (and more?), evaluate the constraints in constraints['sky']
     # maybe even split this method into sub methods for the very distinct sky constraints: min_calibrator_elevation, min_target_elevation, transit_offset & min_distance
-    return True # for now, ignore sky contraints.
+
+    beam = scheduling_unit.requirements_doc['tasks']['Observation']['specifications_doc']['tile_beam']
+    angle1 = beam['angle1']
+    angle2 = beam['angle2']
+    direction_type = beam['direction_type']
+    if "sky" in constraints and 'min_distance' in constraints['sky']:
+        distances = coordinates_and_timestamps_to_separation_from_bodies(angle1=angle1, angle2=angle2, direction_type=direction_type, timestamps=(lower_bound, upper_bound), bodies=tuple(constraints['sky']['min_distance'].keys()))
+        for body, timestamps in distances.items():
+            for timestamp, angle in timestamps.items():
+                min_distance = constraints['sky']['min_distance'][body]
+                if angle.rad < min_distance:
+                    logger.info('Distance=%s from body=%s does not meet min_distance=%s constraint at timestamp=%s' % (angle.rad, body, min_distance, timestamp))
+                    return False
+
+    return True
 
 
 def get_earliest_possible_start_time(scheduling_unit: models.SchedulingUnitBlueprint, lower_bound: datetime) -> datetime:
@@ -147,29 +161,41 @@ def get_earliest_possible_start_time(scheduling_unit: models.SchedulingUnitBluep
         if 'after' in constraints['time']:
             return parser.parse(constraints['time']['after'], ignoretz=True)
 
-        if constraints['daily']['require_day'] or constraints['daily']['require_night']:
+        if constraints['daily']['require_day'] or constraints['daily']['require_night'] or constraints['daily']['avoid_twilight']:
 
             # TODO: TMSS-254 and TMSS-255
-            # TODO: take avoid_twilight into account
-            # for now, use the incorrect proof of concept which works for the demo
-            # but... this should be rewritten completely using Joerns new sun_events
-            LOFAR_CENTER_OBSERVER = create_astroplan_observer_for_station('CS002')
-            sun_events = timestamps_and_stations_to_sun_rise_and_set(timestamps=[lower_bound], stations=['CS002'])['CS002']
-            sun_set = sun_events['sunset'][0]['start']
-            sun_rise = sun_events['sunrise'][0]['end']
+            # TODO: make sure contraints are met by all stations of this observation, not just CS002.
+            sun_events = timestamps_and_stations_to_sun_rise_and_set(timestamps=(lower_bound,lower_bound+timedelta(days=1)), stations=('CS002',))['CS002']
+            day = sun_events['day'][0]
+            night = sun_events['night'][0]
+            next_day = sun_events['day'][1]
+            next_night = sun_events['night'][1]
             if constraints['daily']['require_day']:
-                if lower_bound+scheduling_unit.duration > sun_set:
-                    return LOFAR_CENTER_OBSERVER.sun_rise_time(time=Time(sun_set), which='next').to_datetime()
-                if lower_bound >= sun_rise:
+                # TODO: Do we need to check for observations that are too long and can e.g. only be run in summer?
+                if lower_bound+scheduling_unit.duration > day['end']:
+                    return next_day['start']
+                if lower_bound >= day['start']:
                     return lower_bound
-                return sun_rise
+                return day['start']
 
             if constraints['daily']['require_night']:
-                if lower_bound+scheduling_unit.duration < sun_rise:
+                if lower_bound + scheduling_unit.duration > night['end']:
+                    return next_night['start']
+                if lower_bound >= night['start']:
                     return lower_bound
-                if lower_bound >= sun_set:
-                    return lower_bound
-                return sun_set
+                return night['start']
+
+            if constraints['daily']['avoid_twilight']:
+                if lower_bound + scheduling_unit.duration < day['end']:
+                    if lower_bound >= day['start']:
+                        return lower_bound
+                    return day['start']
+                if lower_bound + scheduling_unit.duration < night['end']:
+                    if lower_bound >= night['start']:
+                        return lower_bound
+                    return night['start']
+                return next_day['start']
+
     except Exception as e:
         logger.exception(str(e))
 
diff --git a/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py b/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py
index 81acf398781285a91fefad08e53db84778fc256e..5d95558568f61159c5975fcb073b7fd0a12ca3c0 100755
--- a/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py
+++ b/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py
@@ -19,6 +19,9 @@
 
 import unittest
 import uuid
+from unittest import mock
+
+from astropy.coordinates import Angle
 
 import logging
 logger = logging.getLogger(__name__)
@@ -61,7 +64,7 @@ from lofar.common.postgres import PostgresDatabaseConnection
 # the module under test
 from lofar.sas.tmss.services.scheduling.dynamic_scheduling import *
 
-
+@unittest.skip('Disabled until scheduler can deal with failing constraints. (Currently causes infinite loop.)')
 class TestDynamicScheduling(unittest.TestCase):
     '''
     Tests for the Dynamic Scheduling
@@ -267,6 +270,77 @@ class TestDynamicScheduling(unittest.TestCase):
         # ensure DEFAULT_INTER_OBSERVATION_GAP between them
         self.assertGreaterEqual(scheduling_unit_blueprint_high.start_time - scheduling_unit_blueprint_manual.stop_time, DEFAULT_INTER_OBSERVATION_GAP)
 
+
+class TestSchedulingConstraints(unittest.TestCase):
+    '''
+    Tests for the constraint checkers used in dynamic scheduling
+    '''
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        cls.obs_duration = 120 * 60
+        scheduling_set = models.SchedulingSet.objects.create(**SchedulingSet_test_data())
+        scheduling_unit_draft = TestDynamicScheduling.create_simple_observation_scheduling_unit("scheduling unit for contraints tests",
+                                                                                                scheduling_set=scheduling_set,
+                                                                                                obs_duration=cls.obs_duration)
+        cls.scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft)
+
+    def setUp(self) -> None:
+        self.sunrise_data = {
+            'CS002': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)},{"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}],
+                      "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}],
+                      "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}],
+                      "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 2, 17, 30, 0), "end": datetime(2020, 1, 3, 7, 30, 0)}]}}
+        self.sunrise_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.timestamps_and_stations_to_sun_rise_and_set')
+        self.sunrise_mock = self.sunrise_patcher.start()
+        self.sunrise_mock.return_value = self.sunrise_data
+        self.addCleanup(self.sunrise_patcher.stop)
+
+        self.distance_data = {
+           "sun": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.3rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.35rad")},
+           "moon": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.2rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.25rad")},
+           "jupiter": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.1rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.15rad")}
+        }
+        self.distance_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.coordinates_and_timestamps_to_separation_from_bodies')
+        self.distance_mock = self.distance_patcher.start()
+        self.distance_mock.return_value = self.distance_data
+        self.addCleanup(self.distance_patcher.stop)
+
+    def test_get_earliest_possible_start_time_with_daytime_constraint_timestamp_returns_day_start(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True
+        self.scheduling_unit_blueprint.save()
+        timestamp = datetime(2020, 1, 1, 4, 0, 0)
+        returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp)
+        self.assertEqual(returned_time, self.sunrise_data['CS002']['day'][0]['start'])
+
+    def test_get_earliest_possible_start_time_with_daytime_constraint_timestamp_returns_timestamp(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True
+        self.scheduling_unit_blueprint.save()
+        timestamp = datetime(2020, 1, 1, 10, 0, 0)
+        returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp)
+        self.assertEqual(returned_time, timestamp)
+
+    # todo: add more daytime checks with 255
+
+    # todo: add nighttime checks with 254
+
+    # todo: add twilight checks with 256
+
+    def test_can_run_within_timewindow_with_min_distance_constraint_returns_true_when_met(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {'sun': 0.1, 'moon': 0.1, 'jupiter': 0.1}
+        self.scheduling_unit_blueprint.save()
+        timestamp = datetime(2020, 1, 1, 10, 0, 0)
+        returned_value = can_run_within_timewindow(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration))
+        self.assertTrue(returned_value)
+
+    def test_can_run_within_timewindow_with_min_distance_constraint_returns_false_when_not_met(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {'sun': 0.2, 'moon': 0.2, 'jupiter': 0.2}
+        self.scheduling_unit_blueprint.save()
+        timestamp = datetime(2020, 1, 1, 10, 0, 0)
+        returned_value = can_run_within_timewindow(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration))
+        self.assertFalse(returned_value)
+
+
 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
 
 if __name__ == '__main__':
diff --git a/SAS/TMSS/src/tmss/tmssapp/conversions.py b/SAS/TMSS/src/tmss/tmssapp/conversions.py
index ab8437eb1fa304aa6690c73d83974e6fb9130a24..af5d004637c17f20118bd660e4e761b22fef288a 100644
--- a/SAS/TMSS/src/tmss/tmssapp/conversions.py
+++ b/SAS/TMSS/src/tmss/tmssapp/conversions.py
@@ -1,9 +1,11 @@
 from astropy.time import Time
 import astropy.units
-from datetime import datetime
+from datetime import datetime, timedelta, time as dtime
 from astropy.coordinates.earth import EarthLocation
-from astropy.coordinates import Angle
+from astropy.coordinates import Angle, get_body
 from astroplan.observer import Observer
+import astropy.time
+from functools import lru_cache
 
 import logging
 logger = logging.getLogger(__name__)
@@ -20,25 +22,28 @@ def create_astroplan_observer_for_station(station: str) -> Observer:
     observer = Observer(location, name="LOFAR", timezone="UTC")
     return observer
 
+
 # default angle to the horizon at which the sunset/sunrise starts and ends, as per LOFAR definition.
 SUN_SET_RISE_ANGLE_TO_HORIZON = Angle(10, unit=astropy.units.deg)
+SUN_SET_RISE_PRECISION = 30  # n_grid_points; higher is more precise but very costly; astropy defaults to 150, errors now can be in the minutes, increase if this is not good enough
 
-def timestamps_and_stations_to_sun_rise_and_set(timestamps: [datetime], stations: [str], angle_to_horizon: Angle=SUN_SET_RISE_ANGLE_TO_HORIZON) -> dict:
+@lru_cache(maxsize=256, typed=False)  # does not like lists, so use tuples to allow caching
+def timestamps_and_stations_to_sun_rise_and_set(timestamps: tuple, stations: tuple, angle_to_horizon: Angle=SUN_SET_RISE_ANGLE_TO_HORIZON) -> dict:
     """
     compute sunrise, sunset, day and night of the given stations at the given timestamps
-    :param timestamps: list of datetimes, e.g. [datetime(2020, 1, 1), datetime(2020, 1, 2)]
-    :param stations: list of station names, e.g. ["CS001"]
+    :param timestamps: tuple of datetimes, e.g. (datetime(2020, 1, 1), datetime(2020, 1, 2))
+    :param stations: tuple of station names, e.g. ("CS002",)
     :return A dict that maps station names to a nested dict that contains lists of start and end times for sunrise, sunset, etc, on each requested date.
         E.g.
-        {"CS001":
-            {   "sunrise": [{"start": (2020, 1, 1, 6, 0, 0)), "end": (2020, 1, 1, 6, 30, 0)},
-                            {"start": (2020, 1, 2, 6, 0, 0)), "end": (2020, 1, 2, 6, 30, 0)}],
-                "sunset": [{"start": (2020, 1, 1, 18, 0, 0)), "end": (2020, 1, 1, 18, 30, 0)},
-                           {"start": (2020, 1, 2, 18, 0, 0)), "end": (2020, 1, 2, 18, 30, 0)}],
-                "day": [{"start": (2020, 1, 1, 6, 30, 0)), "end": (2020, 1, 1, 18, 00, 0)},
-                        {"start": (2020, 1, 2, 6, 30, 0)), "end": (2020, 1, 2, 18, 00, 0)}],
-                "night": [{"start": (2020, 1, 1, 18, 30, 0)), "end": (2020, 1, 2, 6, 0, 0)},
-                          {"start": (2020, 1, 2, 18,3 0, 0)), "end": (2020, 1, 3, 6, 0, 0)}],
+        {"CS002":
+            {   "sunrise": [{"start": datetime(2020, 1, 1, 6, 0, 0)), "end": datetime(2020, 1, 1, 6, 30, 0)},
+                            {"start": datetime(2020, 1, 2, 6, 0, 0)), "end": datetime(2020, 1, 2, 6, 30, 0)}],
+                "sunset": [{"start": datetime(2020, 1, 1, 18, 0, 0)), "end": datetime(2020, 1, 1, 18, 30, 0)},
+                           {"start": datetime(2020, 1, 2, 18, 0, 0)), "end": datetime(2020, 1, 2, 18, 30, 0)}],
+                "day": [{"start": datetime(2020, 1, 1, 6, 30, 0)), "end": datetime(2020, 1, 1, 18, 00, 0)},
+                        {"start": datetime(2020, 1, 2, 6, 30, 0)), "end": datetime(2020, 1, 2, 18, 00, 0)}],
+                "night": [{"start": datetime(2020, 1, 1, 18, 30, 0)), "end": datetime(2020, 1, 2, 6, 0, 0)},
+                          {"start": datetime(2020, 1, 2, 18,3 0, 0)), "end": datetime(2020, 1, 3, 6, 0, 0)}],
             }
         }
     """
@@ -46,15 +51,15 @@ def timestamps_and_stations_to_sun_rise_and_set(timestamps: [datetime], stations
     for station in stations:
         for timestamp in timestamps:
             observer = create_astroplan_observer_for_station(station)
-            sunrise_start = observer.sun_rise_time(time=Time(timestamp), which='previous')
+            sunrise_start = observer.sun_rise_time(time=Time(timestamp), which='previous', n_grid_points=SUN_SET_RISE_PRECISION)
             if sunrise_start.to_datetime().date() < timestamp.date():
-                sunrise_start = observer.sun_rise_time(time=Time(timestamp), horizon=-angle_to_horizon, which='nearest')
+                sunrise_start = observer.sun_rise_time(time=Time(timestamp), horizon=-angle_to_horizon, which='nearest', n_grid_points=SUN_SET_RISE_PRECISION)
             if sunrise_start.to_datetime().date() < timestamp.date():
-                sunrise_start = observer.sun_rise_time(time=Time(timestamp), horizon=-angle_to_horizon, which='next')
-            sunrise_end = observer.sun_rise_time(time=Time(timestamp), horizon=angle_to_horizon, which='next')
-            sunset_start = observer.sun_set_time(time=sunrise_end, horizon=angle_to_horizon, which='next')
-            sunset_end = observer.sun_set_time(time=sunrise_end, horizon=-angle_to_horizon, which='next')
-            sunrise_next_start = observer.sun_rise_time(time=sunset_end, horizon=-angle_to_horizon, which='next')
+                sunrise_start = observer.sun_rise_time(time=Time(timestamp), horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
+            sunrise_end = observer.sun_rise_time(time=Time(timestamp), horizon=angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
+            sunset_start = observer.sun_set_time(time=sunrise_end, horizon=angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
+            sunset_end = observer.sun_set_time(time=sunrise_end, horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
+            sunrise_next_start = observer.sun_rise_time(time=sunset_end, horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
             return_dict.setdefault(station, {}).setdefault("sunrise", []).append({"start": sunrise_start.to_datetime(), "end": sunrise_end.to_datetime()})
             return_dict[station].setdefault("sunset", []).append({"start": sunset_start.to_datetime(), "end": sunset_end.to_datetime()})
             return_dict[station].setdefault("day", []).append({"start": sunrise_end.to_datetime(), "end": sunset_start.to_datetime()})
@@ -62,6 +67,39 @@ def timestamps_and_stations_to_sun_rise_and_set(timestamps: [datetime], stations
     return return_dict
 
 
+# Depending on usage patterns, we should consider refactoring this a little so that we cache on a function with a single timestamp as input. Requests with similar (but not identical) timestamps or bodies currently make no use of cached results for the subset computed in previous requests.
+@lru_cache(maxsize=256, typed=False)  # does not like lists, so use tuples to allow caching
+def coordinates_and_timestamps_to_separation_from_bodies(angle1: float, angle2: float, direction_type: str, timestamps: tuple, bodies: tuple) -> dict:
+    """
+    compute angular distances of the given sky coordinates from the given solar system bodies at the given timestamps (seen from LOFAR core)
+    :param angle1: first angle of celectial coordinates, e.g. RA
+    :param angle2: second angle of celectial coordinates, e.g. Dec
+    :param direction_type: direction_type of celectial coordinates, e.g. 'J2000'
+    :param timestamps: tuple of datetimes, e.g. (datetime(2020, 1, 1, 15, 0, 0), datetime(2020, 1, 1, 16, 0, 0))
+    :param bodies: tuple of solar system bodies, e.g. ('sun', 'moon', 'jupiter')
+    :return A dict that maps each body to a dict that maps the given timestamp to a separation angle from the given coordinate.
+        E.g.
+        {
+           "sun": {datetime(2020, 1, 1, 6, 0, 0): Angle("0.7rad"), datetime(2020, 1, 1, 7, 0, 0): Angle("0.7rad")},
+           "moon": {datetime(2020, 1, 1, 6, 0, 0): Angle("0.4rad"), datetime(2020, 1, 1, 7, 0, 0): Angle("0.4rad")},
+           "jupiter": {datetime(2020, 1, 1, 6, 0, 0): Angle("2.7rad"), datetime(2020, 1, 1, 7, 0, 0): Angle("2.7rad")}
+        }
+    """
+    if direction_type == "J2000":
+        coord = astropy.coordinates.SkyCoord(ra=angle1, dec=angle2, unit=astropy.units.deg)
+    else:
+        raise ValueError("Do not know how to convert direction_type=%s to SkyCoord" % direction_type)
+    return_dict = {}
+    for body in bodies:
+        location = create_astroplan_observer_for_station("CS002").location
+        for timestamp in timestamps:
+            # get body coords at timestamp
+            body_coord = get_body(body=body, time=astropy.time.Time(timestamp), location=location)
+            angle = coord.separation(body_coord)
+            return_dict.setdefault(body, {})[timestamp] = angle
+    return return_dict
+
+
 def local_sidereal_time_for_utc_and_station(timestamp: datetime = None,
                                             station: str = 'CS002',
                                             field: str = 'LBA',
diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py
index f375f739ae5a435eff01474107753925b5b4208f..4ae9725ed8dbaa5c4897e7d309adfdb2fe9ff126 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-11-17 13:44
+# Generated by Django 3.0.9 on 2020-11-24 11:24
 
 from django.conf import settings
 import django.contrib.postgres.fields
@@ -369,6 +369,7 @@ class Migration(migrations.Migration):
                 ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128, primary_key=True, serialize=False)),
                 ('priority_rank', models.FloatField(help_text='Priority of this project w.r.t. other projects. Projects can interrupt observations of lower-priority projects.')),
                 ('trigger_priority', models.IntegerField(default=1000, help_text='Priority of this project w.r.t. triggers.')),
+                ('auto_ingest', models.BooleanField(default=False, help_text='True if The data is ingested when the other scheduling unit tasks are finished. False if The data is ingested after approval in the QA validation workflow. At the end of this a flag is set that the data can be ingested.')),
                 ('can_trigger', models.BooleanField(default=False, help_text='True if this project is allowed to supply observation requests on the fly, possibly interrupting currently running observations (responsive telescope).')),
                 ('private_data', models.BooleanField(default=True, help_text='True if data of this project is sensitive. Sensitive data is not made public.')),
                 ('expert', models.BooleanField(default=False, help_text='Expert projects put more responsibility on the PI.')),
@@ -506,6 +507,8 @@ class Migration(migrations.Migration):
                 ('description', models.CharField(blank=True, default='', help_text='A longer description of this object.', max_length=255)),
                 ('requirements_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Scheduling and/or quality requirements for this scheduling unit (IMMUTABLE).')),
                 ('do_cancel', models.BooleanField()),
+                ('ingest_permission_required', models.BooleanField(default=False, help_text='Explicit permission is needed before the task.')),
+                ('ingest_permission_granted_since', models.DateTimeField(help_text='Moment of object creation.', null=True)),
             ],
             options={
                 'abstract': False,
@@ -523,6 +526,7 @@ class Migration(migrations.Migration):
                 ('requirements_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Scheduling and/or quality requirements for this run.')),
                 ('generator_instance_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Parameter value that generated this run draft (NULLable).', null=True)),
                 ('scheduling_constraints_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Scheduling Constraints for this run.', null=True)),
+                ('ingest_permission_required', models.BooleanField(default=False, help_text='Explicit permission is needed before the task.')),
             ],
             options={
                 'abstract': False,
diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
index f2f04f15009885ff04473a3601256dd878108803..503bb9dbe020d3e462d936e5dc2f47052a1453d5 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
@@ -411,6 +411,7 @@ class Project(NamedCommonPK):
     cycles = ManyToManyField('Cycle', related_name='projects', null=True, help_text='Cycles to which this project belongs (NULLable).')
     priority_rank = FloatField(null=False, help_text='Priority of this project w.r.t. other projects. Projects can interrupt observations of lower-priority projects.') # todo: add if needed: validators=[MinValueValidator(0.0), MaxValueValidator(1.0)]
     trigger_priority = IntegerField(default=1000, help_text='Priority of this project w.r.t. triggers.') # todo: verify meaning and add to help_text: "Triggers with higher priority than this threshold can interrupt observations of projects."
+    auto_ingest = BooleanField(default=False, help_text='True if The data is ingested when the other scheduling unit tasks are finished. False if The data is ingested after approval in the QA validation workflow. At the end of this a flag is set that the data can be ingested.')
     can_trigger = BooleanField(default=False, help_text='True if this project is allowed to supply observation requests on the fly, possibly interrupting currently running observations (responsive telescope).')
     private_data = BooleanField(default=True, help_text='True if data of this project is sensitive. Sensitive data is not made public.')
     expert = BooleanField(default=False, help_text='Expert projects put more responsibility on the PI.')
@@ -516,6 +517,7 @@ class SchedulingUnitDraft(NamedCommon):
     observation_strategy_template = ForeignKey('SchedulingUnitObservingStrategyTemplate', on_delete=PROTECT, null=True, help_text='Observation Strategy Template used to create the requirements_doc.')
     scheduling_constraints_doc = JSONField(help_text='Scheduling Constraints for this run.', null=True)
     scheduling_constraints_template = ForeignKey('SchedulingConstraintsTemplate', on_delete=CASCADE, null=True, help_text='Schema used for scheduling_constraints_doc.')
+    ingest_permission_required = BooleanField(default=False, help_text='Explicit permission is needed before the task.')
 
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
         if self.requirements_doc is not None and self.requirements_template_id and self.requirements_template.schema is not None:
@@ -527,6 +529,11 @@ class SchedulingUnitDraft(NamedCommon):
         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)
 
+        # This code only happens if the objects is not in the database yet. self._state.adding is True creating
+        if self._state.adding and hasattr(self, 'scheduling_set') and self.scheduling_set.project.auto_ingest is False:
+            #When auto_ingest=False, the scheduling units will be created with ingest_permission_required = True
+            self.ingest_permission_required=True
+    
         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)
@@ -574,11 +581,19 @@ class SchedulingUnitBlueprint(NamedCommon):
 
     requirements_doc = JSONField(help_text='Scheduling and/or quality requirements for this scheduling unit (IMMUTABLE).')
     do_cancel = BooleanField()
+    ingest_permission_required = BooleanField(default=False, help_text='Explicit permission is needed before the task.')
+    ingest_permission_granted_since = DateTimeField(auto_now_add=False, null=True, help_text='Moment of object creation.')
     requirements_template = ForeignKey('SchedulingUnitTemplate', on_delete=CASCADE, help_text='Schema used for requirements_doc (IMMUTABLE).')
     draft = ForeignKey('SchedulingUnitDraft', related_name='scheduling_unit_blueprints', on_delete=CASCADE, help_text='Scheduling Unit Draft which this run instantiates.')
 
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
         annotate_validate_add_defaults_to_doc_using_template(self, 'requirements_doc', 'requirements_template')
+       
+        # This code only happens if the objects is not in the database yet. self._state.adding is True creating
+        if self._state.adding and hasattr(self, 'draft') and self.draft.scheduling_set.project.auto_ingest is False:
+            #When auto_ingest=False, the scheduling units will be created with ingest_permission_required = True
+            self.ingest_permission_required=True
+        
 
         super().save(force_insert, force_update, using, update_fields)
 
diff --git a/SAS/TMSS/src/tmss/tmssapp/views.py b/SAS/TMSS/src/tmss/tmssapp/views.py
index af0fdfe11678419260d4603c189b25c4e7b49e74..3c927861dc1153f3563613e4696b8f7d1f5565f6 100644
--- a/SAS/TMSS/src/tmss/tmssapp/views.py
+++ b/SAS/TMSS/src/tmss/tmssapp/views.py
@@ -12,9 +12,12 @@ from rest_framework.permissions import AllowAny
 from rest_framework.decorators import authentication_classes, permission_classes
 from django.apps import apps
 
+from rest_framework.decorators import api_view
 from datetime import datetime
 import dateutil.parser
-from lofar.sas.tmss.tmss.tmssapp.conversions import local_sidereal_time_for_utc_and_station, local_sidereal_time_for_utc_and_longitude, timestamps_and_stations_to_sun_rise_and_set
+from lofar.sas.tmss.tmss.tmssapp.conversions import local_sidereal_time_for_utc_and_station, local_sidereal_time_for_utc_and_longitude, timestamps_and_stations_to_sun_rise_and_set, coordinates_and_timestamps_to_separation_from_bodies
+
+# Note: Decorate with @api_view to get this picked up by Swagger
 
 def subtask_template_default_specification(request, subtask_template_pk:int):
     subtask_template = get_object_or_404(models.SubtaskTemplate, pk=subtask_template_pk)
@@ -46,9 +49,11 @@ def task_specify_observation(request, pk=None):
 # Allow everybody to GET our publicly available template-json-schema's
 @permission_classes([AllowAny])
 @authentication_classes([AllowAny])
-@swagger_auto_schema(responses={200: 'Get the JSON schema from the template with the requested <template>, <name> and <version>',
+@swagger_auto_schema(#method='GET',
+                     responses={200: 'Get the JSON schema from the template with the requested <template>, <name> and <version>',
                                 404: 'the schema with requested <template>, <name> and <version> is not available'},
                      operation_description="Get the JSON schema for the given <template> with the given <name> and <version> as application/json content response.")
+#@api_view(['GET'])   # todo: !! decorating this as api_view somehow breaks json ref resolution !! fix this and double url issue in urls.py, then use decorator here to include in Swagger
 def get_template_json_schema(request, template:str, name:str, version:str):
     template_model = apps.get_model("tmssapp", template)
     template_instance = get_object_or_404(template_model, name=name, version=version)
@@ -65,9 +70,11 @@ def get_template_json_schema(request, template:str, name:str, version:str):
 # Allow everybody to GET our publicly available station group lookups
 @permission_classes([AllowAny])
 @authentication_classes([AllowAny])
-@swagger_auto_schema(responses={200: 'A JSON object with two properties: group:<the_group_name>, stations:<the_list_of_stations>',
+@swagger_auto_schema(#method='GET',
+                     responses={200: 'A JSON object with two properties: group:<the_group_name>, stations:<the_list_of_stations>',
                                 404: 'No such group or template available'},
                      operation_description="Get a JSON list of stations for the given <station_group> name the the group definitions in the common_schema_template given by <template_name> and <template_version>")
+#@api_view(['GET'])  # todo: fix double url issue in urls.py, then use decorator here to include in Swagger
 def get_stations_in_group(request, template_name:str, template_version:str, station_group:str):
     station_schema_template = get_object_or_404(models.CommonSchemaTemplate, name=template_name, version=template_version)
     station_schema = station_schema_template.schema
@@ -88,22 +95,26 @@ def get_stations_in_group(request, template_name:str, template_version:str, stat
 
 @permission_classes([AllowAny])
 @authentication_classes([AllowAny])
-@swagger_auto_schema(responses={200: 'An isoformat timestamp of the current UTC clock of the system'},
+@swagger_auto_schema(method='GET',
+                     responses={200: 'An isoformat timestamp of the current UTC clock of the system'},
                      operation_description="Get the current system time in UTC")
+@api_view(['GET'])
 def utc(request):
     return HttpResponse(datetime.utcnow().isoformat(), content_type='text/plain')
 
 @permission_classes([AllowAny])
 @authentication_classes([AllowAny])
-@swagger_auto_schema(responses={200: 'The LST time in hms format at the given UTC time and station or longitude'},
+@swagger_auto_schema(method='GET',
+                     responses={200: 'The LST time in hms format at the given UTC time and station or longitude'},
                      operation_description="Get LST time for UTC time and station or longitude",
                      manual_parameters=[Parameter(name='station', required=False, type='string', in_='query',
                                                   description="A station names (defaults to CS002)"),
                                         Parameter(name='timestamp', required=False, type='string', in_='query',
                                                   description="A timestamp in isoformat (defaults to utcnow)"),
-                                        Parameter(name='longitude', required=False, type='float', in_='query',
-                                                  description="A longitude")
+                                        Parameter(name='longitude', required=False, type='string', in_='query',
+                                                  description="A longitude as float")
                                         ])
+@api_view(['GET'])
 def lst(request):
     # Handling optional parameters via django paths in urls.py is a pain, we access them on the request directly instead.
     timestamp = request.GET.get('timestamp', None)
@@ -130,12 +141,15 @@ def lst(request):
 
 @permission_classes([AllowAny])
 @authentication_classes([AllowAny])
-@swagger_auto_schema(responses={200: 'A JSON object with sunrise, sunset, day and night of the given stations at the given timestamps'},
-                     operation_description="Get sunrise, sunset, day and night for stations and timestamps",
+@swagger_auto_schema(method='GET',
+                     responses={200: 'A JSON object with sunrise, sunset, day and night of the given stations at the given timestamps'},
+                     operation_description="Get sunrise, sunset, day and night for stations and timestamps.\n\n"
+                                           "Example request: /api/util/sun_rise_and_set?stations=CS002,CS005&timestamps=2020-05-01,2020-09-09T11-11-00",
                      manual_parameters=[Parameter(name='stations', required=False, type='string', in_='query',
                                                   description="comma-separated list of station names"),
                                         Parameter(name='timestamps', required=False, type='string', in_='query',
                                                   description="comma-separated list of isoformat timestamps")])
+@api_view(['GET'])
 def get_sun_rise_and_set(request):
     """
     returns sunrise and sunset at the given stations and timestamps, or today at LOFAR core if none specified.
@@ -144,14 +158,62 @@ def get_sun_rise_and_set(request):
     timestamps = request.GET.get('timestamps', None)
     stations = request.GET.get('stations', None)
     if timestamps is None:
-        timestamps = [datetime.utcnow()]
+        timestamps = (datetime.utcnow(),)
     else:
         timestamps = timestamps.split(',')
-        timestamps = [dateutil.parser.parse(timestamp, ignoretz=True) for timestamp in timestamps]  #  isot to datetime
+        timestamps = tuple([dateutil.parser.parse(timestamp, ignoretz=True) for timestamp in timestamps])  #  isot to datetime
     if stations is None:
-        stations = ['CS002']
+        stations = ("CS002",)
     else:
-        stations = stations.split(',')
+        stations = tuple(stations.split(','))
 
+    # todo: to improve speed for the frontend, we should probably precompute/cache these and return those (where available), to revisit after constraint table / TMSS-190 is done
     return JsonResponse(timestamps_and_stations_to_sun_rise_and_set(timestamps, stations))
 
+
+@permission_classes([AllowAny])
+@authentication_classes([AllowAny])
+@swagger_auto_schema(method='GET',
+                     responses={200: 'A JSON object with angular distances of the given sky coordinates from the given solar system bodies at the given timestamps (seen from LOFAR core)'},
+                     operation_description="Get angular distances of the given sky coordinates from the given solar system bodies at all given timestamps. \n\n"
+                                           "Example request: /api/util/angular_separation_from_bodies?angle1=1&angle2=1&timestamps=2020-01-01T15,2020-01-01T16",
+                     manual_parameters=[Parameter(name='angle1', required=True, type='string', in_='query',
+                                                  description="first angle of celectial coordinates as float, e.g. RA"),
+                                        Parameter(name='angle2', required=True, type='string', in_='query',
+                                                  description="second angle of celectial coordinates as float, e.g. RA"),
+                                        Parameter(name='direction_type', required=False, type='string', in_='query',
+                                                  description="direction_type of celectial coordinates as string, e.g. J2000"),
+                                        Parameter(name='timestamps', required=False, type='string', in_='query',
+                                                  description="comma-separated list of isoformat timestamps"),
+                                        Parameter(name='bodies', required=False, type='string', in_='query',
+                                                  description="comma-separated list of solar system bodies")])
+@api_view(['GET'])
+def get_angular_separation_from_bodies(request):
+    '''
+    returns angular distances of the given sky coordinates from the given astronomical objects at the given timestamps and stations
+    '''
+    timestamps = request.GET.get('timestamps', None)
+    angle1 = request.GET.get('angle1')
+    angle2 = request.GET.get('angle2')
+    direction_type = request.GET.get("direction_type", "J2000")
+    bodies = tuple(request.GET.get('bodies', "sun,moon,jupiter").split(','))
+
+    if angle1 is None or angle2 is None:
+        raise ValueError("Please provide celestial coordinates via 'angle1', 'angle2' (and optionally 'direction_type') properties.")
+
+    if timestamps is None:
+        timestamps = (datetime.utcnow(),)
+    else:
+        timestamps = timestamps.split(',')
+        timestamps = tuple([dateutil.parser.parse(timestamp, ignoretz=True) for timestamp in timestamps])  #  isot to datetime
+
+    # calculate
+    sep_dict = coordinates_and_timestamps_to_separation_from_bodies(angle1=angle1, angle2=angle2, direction_type=direction_type, bodies=bodies, timestamps=timestamps)
+    new_sep_dict = {}
+
+    # serialize angles and datetimes for json response
+    for body, timestamps in sep_dict.items():
+        for timestamp, angle in timestamps.items():
+            new_sep_dict.setdefault(body, {})[timestamp.isoformat()] = angle.rad
+
+    return JsonResponse(new_sep_dict)
diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py
index 1327d5b5a41ba2e80d100c254ef60c7ddc91aa0b..623d43642732d4a11463f252adffb0938259d9c9 100644
--- a/SAS/TMSS/src/tmss/urls.py
+++ b/SAS/TMSS/src/tmss/urls.py
@@ -53,6 +53,7 @@ swagger_schema_view = get_schema_view(
   # permission_classes=(permissions.AllowAny,),
 )
 
+# use re_path(r'<...>/?') to make trailing slash optional (double entries confuse Swagger)
 urlpatterns = [
     path('admin/', admin.site.urls),
     path('logout/', LogoutView.as_view(), name='logout'),
@@ -60,13 +61,16 @@ urlpatterns = [
     re_path(r'^swagger(?P<format>\.json|\.yaml)$', swagger_schema_view.without_ui(cache_timeout=0), name='schema-json'),
     path('swagger/', swagger_schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
     path('redoc/', swagger_schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
-    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('station_groups/<str:template_name>/<str:template_version>/<str:station_group>', views.get_stations_in_group, name='get_stations_in_group'), #TODO: how to make trailing slash optional?
+    #re_path('schemas/<str:template>/<str:name>/<str:version>', views.get_template_json_schema, name='get_template_json_schema'),  # !! use of regex here breaks reverse url lookup
+    path('schemas/<str:template>/<str:name>/<str:version>', views.get_template_json_schema, name='get_template_json_schema'),   # !! two urls for same view break Swagger, one url break json ref resolution !!
+    path('schemas/<str:template>/<str:name>/<str:version>/', views.get_template_json_schema, name='get_template_json_schema'),  # !! two urls for same view break Swagger, one url break json ref resolution !!
+    #re_path('station_groups/<str:template_name>/<str:template_version>/<str:station_group>/?', views.get_stations_in_group, name='get_stations_in_group'), # !! use of regex here somehow breaks functionality (because parameters?) -> index page
+    path('station_groups/<str:template_name>/<str:template_version>/<str:station_group>', views.get_stations_in_group, name='get_stations_in_group'),
     path('station_groups/<str:template_name>/<str:template_version>/<str:station_group>/', views.get_stations_in_group, name='get_stations_in_group'),
-    path('util/sun_rise_and_set', views.get_sun_rise_and_set, name='get_sun_rise_and_set'),
-    path(r'util/utc', views.utc, name="system-utc"),
-    path(r'util/lst', views.lst, name="conversion-lst"),
+    re_path('util/sun_rise_and_set/?', views.get_sun_rise_and_set, name='get_sun_rise_and_set'),
+    re_path('util/utc/?', views.utc, name="system-utc"),
+    re_path('util/lst/?', views.lst, name="conversion-lst"),
+    re_path('util/angular_separation_from_bodies/?', views.get_angular_separation_from_bodies, name='get_angular_separation_from_bodies'),
 ]
 
 if os.environ.get('SHOW_DJANGO_DEBUG_TOOLBAR', False):
@@ -235,4 +239,4 @@ if bool(os.environ.get('TMSS_ENABLE_VIEWFLOW', False)):
     viewflow_router.register('scheduling_unit_flow/qa_scheduling_unit_process', workflow_viewsets.SchedulingUnitProcessViewSet, basename='qa_scheduling_unit_process')
 
     urlpatterns.extend([url(r'^workflow$', RedirectView.as_view(url='/workflow/', permanent=False)),
-                        url(r'^workflow_api/', include(viewflow_router.urls))])
\ No newline at end of file
+                        url(r'^workflow_api/', include(viewflow_router.urls))])
diff --git a/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py
index 1da372c3f5a8ea06e95f13d9861676f8bcdf8636..cdea4f733fe87cb65a93715ce9fe5f4ebf25f750 100644
--- a/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py
+++ b/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.0.9 on 2020-11-02 14:31
+# Generated by Django 3.0.9 on 2020-11-24 11:24
 
 from django.db import migrations, models
 import django.db.models.deletion
diff --git a/SAS/TMSS/test/t_conversions.py b/SAS/TMSS/test/t_conversions.py
index 14231c4f091c04b1f3c53b971bbf069555e6000f..f153900312eac5e6ebab6a268c80386892983c26 100755
--- a/SAS/TMSS/test/t_conversions.py
+++ b/SAS/TMSS/test/t_conversions.py
@@ -165,6 +165,73 @@ class UtilREST(unittest.TestCase):
             response_date = dateutil.parser.parse(r_dict['CS002']['sunrise'][i]['start']).date()
             self.assertEqual(expected_date, response_date)
 
+    def test_util_angular_separation_from_bodies_yields_error_when_no_pointing_is_given(self):
+        r = requests.get(BASE_URL + '/util/angular_separation_from_bodies', auth=AUTH)
+
+        # assert error
+        self.assertEqual(r.status_code, 500)
+        self.assertIn("celestial coordinates", r.content.decode('utf-8'))
+
+    def test_util_angular_separation_from_bodies_returns_json_structure_with_defaults(self):
+        r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?angle1=1&angle2=1', auth=AUTH)
+        self.assertEqual(r.status_code, 200)
+        r_dict = json.loads(r.content.decode('utf-8'))
+
+        # assert default bodies
+        for key in ['sun', 'jupiter', 'moon']:
+            self.assertIn(key, r_dict.keys())
+
+        # assert timestamp is now and has a value
+        returned_datetime = dateutil.parser.parse(list(r_dict['jupiter'].keys())[0])
+        current_datetime = datetime.datetime.utcnow()
+        delta = abs((returned_datetime - current_datetime).total_seconds())
+        self.assertTrue(delta < 60.0)
+        self.assertEqual(type(list(r_dict['jupiter'].values())[0]), float)
+
+    def test_util_angular_separation_from_bodies_considers_bodies(self):
+        bodies = ['sun', 'neptune', 'mercury']
+        r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?angle1=1&angle2=1&bodies=%s' % ','.join(bodies), auth=AUTH)
+        self.assertEqual(r.status_code, 200)
+        r_dict = json.loads(r.content.decode('utf-8'))
+
+        # assert station is included in response and angles differ
+        angle_last = None
+        for body in bodies:
+            self.assertIn(body, r_dict.keys())
+            angle = list(r_dict[body].values())[0]
+            if angle_last:
+                self.assertNotEqual(angle, angle_last)
+            angle_last = angle
+
+    def test_util_angular_separation_from_bodies_considers_timestamps(self):
+        timestamps = ['2020-01-01', '2020-02-22T16-00-00', '2020-3-11', '2020-01-01']
+        r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?angle1=1&angle2=1&timestamps=%s' % ','.join(timestamps), auth=AUTH)
+        self.assertEqual(r.status_code, 200)
+        r_dict = json.loads(r.content.decode('utf-8'))
+
+        # assert all requested timestamps yield a response and angles differ
+        angle_last = None
+        for timestamp in timestamps:
+            expected_timestamp = dateutil.parser.parse(timestamp, ignoretz=True).isoformat()
+            self.assertIn(expected_timestamp, list(r_dict['jupiter'].keys()))
+            angle = r_dict['jupiter'][expected_timestamp]
+            if angle_last:
+                self.assertNotEqual(angle, angle_last)
+            angle_last = angle
+
+    def test_util_angular_separation_from_bodies_considers_coordinates(self):
+        test_coords = [(1, 1,"J2000"), (1.1, 1, "J2000"), (1.1, 1.1, "J2000")]
+        for coords in test_coords:
+            r = requests.get(BASE_URL + '/util/angular_separation_from_bodies?angle1=%s&angle2=%s&direction_type=%s' % coords, auth=AUTH)
+            self.assertEqual(r.status_code, 200)
+            r_dict = json.loads(r.content.decode('utf-8'))
+
+            # assert all requested timestamps yield a response and angles differ
+            angle_last = None
+            angle = list(r_dict['jupiter'].values())[0]
+            if angle_last:
+                self.assertNotEqual(angle, angle_last)
+            angle_last = angle
 
 if __name__ == "__main__":
     os.environ['TZ'] = 'UTC'
diff --git a/SAS/TMSS/test/t_tmssapp_specification_django_API.py b/SAS/TMSS/test/t_tmssapp_specification_django_API.py
index c5c917319778ad16ef17e94331674b10af68309b..98bb7aad6b8dc43efef99e435ee2ebcb27cbb38b 100755
--- a/SAS/TMSS/test/t_tmssapp_specification_django_API.py
+++ b/SAS/TMSS/test/t_tmssapp_specification_django_API.py
@@ -446,6 +446,15 @@ class SchedulingUnitDraftTest(unittest.TestCase):
         with self.assertRaises(IntegrityError):
             models.SchedulingUnitDraft.objects.create(**test_data)
 
+    def test_SchedulingUnitDraft_gets_created_with_correct_default_ingest_permission_required(self):
+        
+        # setup
+        entry = models.SchedulingUnitDraft.objects.create(**SchedulingUnitDraft_test_data())
+        #check the auto_ingest on project
+        self.assertEqual(False, entry.scheduling_set.project.auto_ingest)
+        #When auto_ingest=False (in project), the scheduling units should be created with ingest_permission_required = True
+        self.assertEqual(True, entry.ingest_permission_required)
+
 
 class TaskDraftTest(unittest.TestCase):
 
@@ -650,6 +659,16 @@ class SchedulingUnitBlueprintTest(unittest.TestCase):
         # assert
         with self.assertRaises(IntegrityError):
             models.SchedulingUnitBlueprint.objects.create(**test_data)
+    
+    
+    def test_SchedulingUnitBlueprint_gets_created_with_correct_default_ingest_permission_required(self):
+
+        # setup
+        entry = models.SchedulingUnitBlueprint.objects.create(**SchedulingUnitBlueprint_test_data())
+        #check the auto_ingest on project
+        self.assertEqual(False, entry.draft.scheduling_set.project.auto_ingest)
+        #When auto_ingest=False (in project), the scheduling units should be created with ingest_permission_required = True
+        self.assertEqual(True, entry.ingest_permission_required)
 
 
 class TaskBlueprintTest(unittest.TestCase):
diff --git a/SAS/TMSS/test/tmss_test_data_django_models.py b/SAS/TMSS/test/tmss_test_data_django_models.py
index 5edc2d0b9a87be9a108937fb0467fdff1476860d..7f5f266be4ac768d006f7860abd9d8f85351c723 100644
--- a/SAS/TMSS/test/tmss_test_data_django_models.py
+++ b/SAS/TMSS/test/tmss_test_data_django_models.py
@@ -126,6 +126,7 @@ def Project_test_data(name: str=None, priority_rank: int = 1, archive_subdirecto
               "name": name,
                "description": 'my description ' + str(uuid.uuid4()),
                "tags": [],
+               "auto_ingest": False,
                "priority_rank": priority_rank,
                "trigger_priority": 1000,
                "can_trigger": False,