diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py index f24068cd0ce5bb6d6e6b6f08d435458e72d3c043..cec4fd45dff78a464c315c5d5715c4d5fa9b4c00 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py @@ -335,30 +335,6 @@ class SchedulingUnitBlueprintSlimSerializer(serializers.ModelSerializer): raise NotImplementedError("This serializer should only be used for fast querying existing scheduling units") -class SchedulingUnitBlueprintPublicSerializer(serializers.ModelSerializer): - '''A read-only serializer exposing public non-sensitive properties.''' - project = serializers.StringRelatedField(source='draft.scheduling_set.project.name', label='project', read_only=True) - on_sky_duration = FloatDurationField(read_only=True) - target_pointings_astropy = serializers.StringRelatedField(label='targets', read_only=True) - - class Meta: - model = models.SchedulingUnitBlueprint - read_only_fields = ['id', - 'status', - 'on_sky_start_time', - 'on_sky_stop_time', - 'on_sky_duration', - 'target_pointings_astropy', - 'project'] - fields = read_only_fields - - def create(self, validated_data): - raise NotImplementedError("This is a read-only serializer") - - def update(self, instance, validated_data): - raise NotImplementedError("This is a read-only serializer") - - class TaskBlueprintSlimSerializer(serializers.ModelSerializer): '''A small 'slim' serializer for the most relevant properties.''' duration = FloatDurationField(read_only=True) diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/views.py b/SAS/TMSS/backend/src/tmss/tmssapp/views.py index 490694437e7350d45c9fff079bf5f7d5ac2606e7..70cf03e6dd61739fe88f317e9d55112313c02698 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/views.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/views.py @@ -755,40 +755,59 @@ def failure_report(request): @swagger_auto_schema(responses={200: 'A json list of the current public schedule.'}, operation_description="Get a json list of the current public schedule.") def public_schedule(request): - '''return the current and future running and scheduled units in either html table or json depending on the request accept header''' + '''return the current and future(2 weeks) running and scheduled units in a simple html table''' + return __create_html_schedule(request, past=False) + + +@permission_classes([AllowAny]) +@authentication_classes([AllowAny]) +@swagger_auto_schema(responses={200: 'A json list of the current public schedule.'}, + operation_description="Get a json list of the current public schedule.") +def public_past_schedule(request): + '''return the past schedule of observed units in the cycle in a simple html table''' + return __create_html_schedule(request, past=True) + + +def __create_html_schedule(request, past: bool=False): # get all current and future running and scheduled units - scheduled_or_running_units = models.SchedulingUnitBlueprint.objects.prefetch_related('task_blueprints__specifications_template__type') \ - .select_related(models.SchedulingUnitBlueprint.path_to_project, 'status', 'specifications_template', 'specifications_template') \ - .filter(status__value__in=models.SchedulingUnitStatus.ACTIVE_STATUS_VALUES) \ - .filter(obsolete_since__isnull=True) \ - .filter(on_sky_stop_time__gte=datetime.utcnow()) \ - .filter(on_sky_start_time__lte=datetime.utcnow()+timedelta(days=7)) \ - .order_by('on_sky_start_time').all() - - # return json if requested if 'json' in request.headers.get('Accept','').lower(): - result = serializers.SchedulingUnitBlueprintPublicSerializer(scheduled_or_running_units, many=True, context={'request': request}).data - return JsonResponse(result, status=status.HTTP_200_OK, safe=False) + return JsonResponse("Sorry, Json is not supported", status=status.HTTP_501_NOT_IMPLEMENTED, safe=False) - # else, return html table base_url = request._current_scheme_host - table_rows = '''<tr><th>Project</th><th>ID</th><th>Name</th><th>Start [UTC]</th><th>End [UTC]</th><th>Duration [min]</th><th>Center [LST]</th><th>Antenna</th><th>Target(s)</th></tr>\n''' + title = 'LOFAR observing schedule %sUTC' %(datetime.utcnow().strftime("%Y-%m-%d %H:%M"), ) + + scheduling_units = models.SchedulingUnitBlueprint.objects.prefetch_related('task_blueprints__specifications_template__type') \ + .select_related(models.SchedulingUnitBlueprint.path_to_project, 'status', 'specifications_template', 'specifications_template') \ + .filter(status__value__in=models.SchedulingUnitStatus.ACTIVE_OR_FINISHED_STATUS_VALUES) \ + .filter(obsolete_since__isnull=True) + if past: + current_cycle = models.Cycle.objects.filter(start__lte=datetime.utcnow()).order_by('-start').first() + scheduling_units = scheduling_units.filter(on_sky_stop_time__gte=current_cycle.start) \ + .filter(on_sky_start_time__lte=datetime.utcnow()) + description = '''This page shows all %s's past observations. Please find the current observations <a href="%s">here</a>''' % (current_cycle.name, base_url.rstrip('/')+'/schedule') + else: + scheduling_units = scheduling_units.filter(on_sky_stop_time__gte=datetime.utcnow()) \ + .filter(on_sky_start_time__lte=datetime.utcnow()+timedelta(days=14)) + description = '''This page shows all observations for the upcoming 2 weeks. Please find all past observations <a href="%s">here</a>''' % (base_url.rstrip('/')+'/past_schedule', ) + description += '''<br>This schedule is subject to change based on changing telescope operational circumstances''' + + scheduling_units = scheduling_units.order_by('on_sky_start_time').all() + + table_rows = '''<tr><th>Project</th><th>ID</th><th>Name</th><th>Status</th><th>Start [UTC]</th><th>End [UTC]</th><th>Antenna</th><th>Target(s)</th></tr>\n''' row_date = date.min - for su in scheduled_or_running_units: + for su in scheduling_units: if su.on_sky_start_time.date() > row_date: row_date = su.on_sky_start_time.date() table_rows += '''<tr><td style="font-weight: bold; background-color: #999999;" colspan=9>%s</td></tr>\n''' % (row_date.strftime('%Y-%m-%d')) - table_rows += '''<tr><td><a href="%s/project/view/%s">%s</a></td><td><a href="%s/schedulingunit/view/blueprint/%s">%s</a></td><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr>\n''' % ( - base_url, su.project, su.project, + table_rows += '''<tr><td><a href="%s/project/view/%s" title="%s">%s</a></td><td><a href="%s/schedulingunit/view/blueprint/%s">%s</a></td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n''' % ( + base_url, su.project.name, 'description hidden' if su.project.private_data else su.project.description , su.project.name, base_url, su.id, su.id, - su.name, + 'hidden' if su.project.private_data else su.name, su.status.value, su.on_sky_start_time.strftime('%H:%M') if su.on_sky_start_time else '', su.on_sky_stop_time.strftime('%H:%M') if su.on_sky_stop_time else '', - round(su.on_sky_duration.total_seconds()/60.0), - su.main_observation_scheduled_central_lst.strftime('%H:%M') if su.main_observation_scheduled_central_lst else '', ', '.join(sorted(list([q for q in set([x['specifications_doc__station_configuration__antenna_set'] for x in su.observation_tasks.values('specifications_doc__station_configuration__antenna_set')]) if q is not None]))), - su.target_pointings_astropy) + 'hidden' if su.project.private_data else su.target_pointings_astropy) html_doc = '''<!DOCTYPE html> <html> @@ -817,15 +836,20 @@ def public_schedule(request): } </style> <title>LOFAR observing schedule %sUTC</title> - <meta http-equiv="refresh" content="60"> + <meta http-equiv="refresh" content="300"> </head> <body> + <h1>%s</h1> + <p>%s</p> <table> %s </table> </body> </html> - ''' % (datetime.utcnow().strftime("%Y-%m-%d %H:%M"), table_rows,) + ''' % (title, + title, + description, + table_rows,) return HttpResponse(html_doc, content_type='text/html') diff --git a/SAS/TMSS/backend/src/tmss/urls.py b/SAS/TMSS/backend/src/tmss/urls.py index 51c1d6601214f0350191dd483b73a441b4107f7f..e691fdc120ff7a55afc0dc512ea3e165065e6d6e 100644 --- a/SAS/TMSS/backend/src/tmss/urls.py +++ b/SAS/TMSS/backend/src/tmss/urls.py @@ -270,6 +270,7 @@ urlpatterns = [url(r'^api$', RedirectView.as_view(url='/api/')), RedirectView.as_view(url='/oidc/')), url(r'^oidc/', include('mozilla_django_oidc.urls')), url(r'^schedule', views.public_schedule), + url(r'^past_schedule', views.public_past_schedule), url(r'', include(frontend_urls)), url(r'^.*', include(frontend_urlpatterns)), ] diff --git a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/login.js b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/login.js index bb5361e6ceb7836b6b941db097c20e4ff7eb0df3..babcc089bfdbab4bb6950290fd3876a21fd0c2ce 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/login.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/login.js @@ -163,6 +163,9 @@ export class Login extends Component { <div className="col-md-6 forget-paswd login-form-link"> <a href={process.env.REACT_APP_FORGOT_PASSWORD_URL} >Forgot Password?</a> </div> + <div className="col-md-6 login-form-link"> + <a href="/schedule">Public Schedule</a> + </div> </div> {this.state.error && <div className="row error">