from django.db import models from django.urls import reverse from django.utils.timezone import datetime from django.db.models import Sum # constants datetime_format_string = '%Y-%m-%dT%H:%M:%SZ' TASK_TYPE_OBSERVATION = 'observation' TASK_TYPE_DATAPRODUCT = 'dataproduct' TYPE_VISIBILITY = 'visibility' # common model functions def get_sum_from_dataproduct_field(taskID, field): """ sum the values of a field for certain taskID. (used to sum total of dataproduct sizes) :param taskID: :param field: :return: """ query = field + '__sum' sum_value = DataProduct.objects.filter(taskID=taskID).aggregate(Sum(field))[query] if sum_value == None: sum_value = 0.0 return sum_value """ a base class of both Observation and Dataproducts """ class TaskObject(models.Model): name = models.CharField(max_length=100, default="unknown") task_type = models.CharField(max_length=20, default=TASK_TYPE_DATAPRODUCT) taskID = models.CharField('runId', db_index=True, max_length=30, blank=True, null=True) 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) irods_collection = models.CharField(max_length=255, default="unknown", null=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) def __str__(self): return str(self.id) 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) @property def property_taskID(self): return self.taskObject.taskID @property def property_task_type(self): return self.taskObject.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) # beamPattern is used to understand how many beams (dataproducts) must be created beamPattern = models.CharField(max_length=50, null=True) # can be used to distinguish types of observations, like for ARTS. process_type = models.CharField(max_length=50, default="observation") 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) control_parameters = models.CharField(max_length=255, default="unknown", null=True) telescopes = models.CharField(max_length=100, default="all", null=True) skip_auto_ingest = models.BooleanField(default=False) beams = models.CharField(max_length=255, default="0..39") quality = models.CharField(max_length=30, default="unknown") ingest_progress = models.CharField(max_length=40, default="", null=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 @property def size(self): # sum the sizes of all dataproducts with this taskID. In Mb size = get_sum_from_dataproduct_field(self.taskID,'size') return size def __str__(self): return str(self.taskID) class DataProduct(TaskObject): # properties filename = models.CharField(max_length=200, default="unknown") description = models.CharField(max_length=255, default="unknown") dataproduct_type = models.CharField('type', default=TYPE_VISIBILITY, max_length=50) size = models.BigIntegerField(default=0) quality = models.CharField(max_length=30, default="unknown") # relationships parent = models.ForeignKey(Observation, related_name='generated_dataproducts', on_delete=models.CASCADE, null=False) # 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('dataproduct-detail-view-api', kwargs={'pk': self.pk}) def __str__(self): return self.filename