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