fix unicode
[qcg-portal.git] / qcg / models.py
index b9a35e5..316f56e 100644 (file)
@@ -1,20 +1,28 @@
 # coding=utf-8
 from django.contrib.auth.models import AbstractUser
 # coding=utf-8
 from django.contrib.auth.models import AbstractUser
+from django.core.urlresolvers import reverse
 from django.db import models
 from django.utils.timezone import now
 from pyqcg.service import Job as QcgJob, Task as QcgTask
 from django.db import models
 from django.utils.timezone import now
 from pyqcg.service import Job as QcgJob, Task as QcgTask
-from pyqcg.utils import JobStatus, TaskStatus, TaskType
-from timedelta import TimedeltaField
+from pyqcg.utils import JobStatus, TaskStatus, TaskType, AllocationType, EPRUtils, DescriptionType
 
 from qcg.utils import username_from_dn, get_attributes
 
 
 
 from qcg.utils import username_from_dn, get_attributes
 
 
-__all__ = ['User', 'Job', 'Task', 'Allocation', 'NodeInfo']
+__all__ = ['User', 'Job', 'Task', 'Allocation', 'NodeInfo', 'JobTemplate']
 
 
 class User(AbstractUser):
     last_update = models.DateTimeField(default=now)
 
 
 
 class User(AbstractUser):
     last_update = models.DateTimeField(default=now)
 
+    @property
+    def tasks(self):
+        return Task.objects.filter(job__owner=self)
+
+
+DESC_TYPE_CHOICES = list(enumerate(field for field in dir(DescriptionType) if not field.startswith('__')))
+DESC_TYPE_CHOICES_REVERSED = {v: k for k, v in DESC_TYPE_CHOICES}
+
 
 class Job(models.Model):
     STATUS_CHOICES = list(enumerate(field for field in dir(JobStatus) if not field.startswith('__')))
 
 class Job(models.Model):
     STATUS_CHOICES = list(enumerate(field for field in dir(JobStatus) if not field.startswith('__')))
@@ -27,10 +35,12 @@ class Job(models.Model):
     status = models.IntegerField(u"Status", choices=STATUS_CHOICES)
     note = models.TextField(u"Notatka", blank=True, default='')
     description = models.TextField(u"Opis", blank=True, default='')
     status = models.IntegerField(u"Status", choices=STATUS_CHOICES)
     note = models.TextField(u"Notatka", blank=True, default='')
     description = models.TextField(u"Opis", blank=True, default='')
+    description_type = models.IntegerField(u"Typ opisu", choices=DESC_TYPE_CHOICES, blank=True, null=True)
+    qcg_description = models.TextField(u"Opis QCG", blank=True, default='')
+    project = models.TextField(u"Projekt", blank=True, default='')
     submission_time = models.DateTimeField(u"Data wysłania")
     finish_time = models.DateTimeField(u"Data zakończenia", blank=True, null=True)
     submission_time = models.DateTimeField(u"Data wysłania")
     finish_time = models.DateTimeField(u"Data zakończenia", blank=True, null=True)
-    proxy_lifetime = TimedeltaField(u"Czas życia proxy", blank=True, null=True)
-    project = models.TextField(u"Projekt", blank=True, default='')
+    proxy_lifetime = models.DateTimeField(u"Czas życia proxy", blank=True, null=True)
     purged = models.BooleanField(u"Usunięty katalog roboczy?", default=False)
 
     owner = models.ForeignKey(User, verbose_name=u"Właściciel", related_name='jobs')
     purged = models.BooleanField(u"Usunięty katalog roboczy?", default=False)
 
     owner = models.ForeignKey(User, verbose_name=u"Właściciel", related_name='jobs')
@@ -45,37 +55,39 @@ class Job(models.Model):
     def __unicode__(self):
         return self.job_id
 
     def __unicode__(self):
         return self.job_id
 
+    def get_absolute_url(self):
+        return reverse('job', kwargs={'job_id': self.job_id})
+
     @property
     def qcg_job(self):
         if self._job is None:
     @property
     def qcg_job(self):
         if self._job is None:
-            self._job = QcgJob(self.epr)
+            self._job = QcgJob(EPRUtils.deserialize_epr(self.epr))
 
         return self._job
 
     @staticmethod
     def qcg_map(qcg_job, user=None):
 
         return self._job
 
     @staticmethod
     def qcg_map(qcg_job, user=None):
-        attrs = get_attributes(qcg_job, ('job_id', 'note', 'description', 'submission_time', 'finish_time',
-                                         'proxy_lifetime', 'project', 'purged'))
+        attrs = get_attributes(qcg_job, ('job_id', 'note', 'description', 'qcg_description', 'submission_time',
+                                         'finish_time', 'project', 'purged'))
 
 
-        attrs['epr'] = str(qcg_job.epr)
+        attrs['epr'] = EPRUtils.serialize_epr(qcg_job.epr)
         attrs['status'] = Job.STATUS_CHOICES_REVERSED[qcg_job.status]
         attrs['status'] = Job.STATUS_CHOICES_REVERSED[qcg_job.status]
+        attrs['description_type'] = DESC_TYPE_CHOICES_REVERSED[qcg_job.description_type]
+        attrs['proxy_lifetime'] = now() + qcg_job.proxy_lifetime
 
         username = username_from_dn(qcg_job.user_dn)
         if user is not None:
             if user.username != username:
 
         username = username_from_dn(qcg_job.user_dn)
         if user is not None:
             if user.username != username:
-                raise ValueError('')
+                raise ValueError('Username does not match ({} vs. {})!'.format(repr(user.username), repr(username)))
             attrs['owner'] = user
         else:
             attrs['owner'] = User.objects.get(username=username)
 
         return attrs
 
             attrs['owner'] = user
         else:
             attrs['owner'] = User.objects.get(username=username)
 
         return attrs
 
-    @classmethod
-    def from_qcg(cls, qcg_job):
-        job = cls(**cls.qcg_map(qcg_job))
-        job._job = qcg_job
-
-        return job
+    @property
+    def terminated(self):
+        return self.get_status_display() in [JobStatus.FINISHED, JobStatus.FAILED, JobStatus.CANCELED]
 
 
 class Task(models.Model):
 
 
 class Task(models.Model):
@@ -95,12 +107,13 @@ class Task(models.Model):
     type = models.IntegerField(u"Typ", choices=TYPE_CHOICES)
     note = models.TextField(u"Notatka", blank=True, default='')
     description = models.TextField(u"Opis", blank=True, default='')
     type = models.IntegerField(u"Typ", choices=TYPE_CHOICES)
     note = models.TextField(u"Notatka", blank=True, default='')
     description = models.TextField(u"Opis", blank=True, default='')
+    description_type = models.IntegerField(u"Typ opisu", choices=DESC_TYPE_CHOICES, blank=True, null=True)
     submission_time = models.DateTimeField(u"Data wysłania")
     start_time = models.DateTimeField(u"Data rozpoczęcia", blank=True, null=True)
     finish_time = models.DateTimeField(u"Data zakończenia", blank=True, null=True)
     reserved_time_start = models.DateTimeField(u"Data rozpoczęcia rezerwacji", blank=True, null=True)
     reserved_time_finish = models.DateTimeField(u"Data zakończenia rezerwacji", blank=True, null=True)
     submission_time = models.DateTimeField(u"Data wysłania")
     start_time = models.DateTimeField(u"Data rozpoczęcia", blank=True, null=True)
     finish_time = models.DateTimeField(u"Data zakończenia", blank=True, null=True)
     reserved_time_start = models.DateTimeField(u"Data rozpoczęcia rezerwacji", blank=True, null=True)
     reserved_time_finish = models.DateTimeField(u"Data zakończenia rezerwacji", blank=True, null=True)
-    proxy_lifetime = TimedeltaField(u"Czas życia proxy", blank=True, null=True)
+    proxy_lifetime = models.DateTimeField(u"Czas życia proxy", blank=True, null=True)
     purged = models.BooleanField(u"Usunięty katalog roboczy?", default=False)
 
     created = models.DateTimeField(u"Utworzono", auto_now_add=True)
     purged = models.BooleanField(u"Usunięty katalog roboczy?", default=False)
 
     created = models.DateTimeField(u"Utworzono", auto_now_add=True)
@@ -115,37 +128,36 @@ class Task(models.Model):
     def __unicode__(self):
         return u'{}/{}'.format(self.job, self.task_id)
 
     def __unicode__(self):
         return u'{}/{}'.format(self.job, self.task_id)
 
+    def get_absolute_url(self):
+        return reverse('task', kwargs={'job_id': self.job.job_id, 'task_id': self.task_id})
+
     @property
     def qcg_task(self):
     @property
     def qcg_task(self):
-        if self._qcg_task is None:
-            self._qcg_task = QcgTask(self.epr)
+        if self._task is None:
+            self._task = QcgTask(EPRUtils.deserialize_epr(self.epr))
 
 
-        return self._qcg_task
+        return self._task
 
     @staticmethod
 
     @staticmethod
-    def qcg_map(qcg_task, jobs=None):
+    def qcg_map(qcg_task, job=None):
         attrs = get_attributes(qcg_task, ('task_id', 'status_description', 'note', 'description', 'submission_time',
         attrs = get_attributes(qcg_task, ('task_id', 'status_description', 'note', 'description', 'submission_time',
-                                          'start_time', 'finish_time', 'reserved_time_slot', 'proxy_lifetime',
-                                          'purged'))
+                                          'start_time', 'finish_time', 'reserved_time_slot', 'purged'))
 
 
-        attrs['epr'] = str(qcg_task.epr)
+        attrs['epr'] = EPRUtils.serialize_epr(qcg_task.epr)
         attrs['status'] = Task.STATUS_CHOICES_REVERSED[qcg_task.status]
         attrs['type'] = Task.TYPE_CHOICES_REVERSED[qcg_task.type]
         attrs['status'] = Task.STATUS_CHOICES_REVERSED[qcg_task.status]
         attrs['type'] = Task.TYPE_CHOICES_REVERSED[qcg_task.type]
+        attrs['description_type'] = DESC_TYPE_CHOICES_REVERSED[qcg_task.description_type]
+        attrs['proxy_lifetime'] = now() + qcg_task.proxy_lifetime
 
 
-        if jobs is not None and qcg_task.job_id in jobs:
-            attrs['job'] = jobs[qcg_task.job_id]
+        if job is not None:
+            if qcg_task.job_id != job.job_id:
+                raise ValueError('Job id does not match ({} vs. {})!'.format(repr(qcg_task.job_id), repr(job.job_id)))
+            attrs['job'] = job
         else:
             attrs['job'] = Job.objects.get(job_id=qcg_task.job_id)
 
         return attrs
 
         else:
             attrs['job'] = Job.objects.get(job_id=qcg_task.job_id)
 
         return attrs
 
-    @classmethod
-    def from_qcg(cls, qcg_task):
-        task = cls(**cls.qcg_map(qcg_task))
-        task._task = qcg_task
-
-        return task
-
     @property
     def reserved_time_slot(self):
         if self.reserved_time_start or self.reserved_time_finish:
     @property
     def reserved_time_slot(self):
         if self.reserved_time_start or self.reserved_time_finish:
@@ -157,17 +169,25 @@ class Task(models.Model):
             self.reserved_time_start, self.reserved_time_finish = value
 
     @property
             self.reserved_time_start, self.reserved_time_finish = value
 
     @property
-    def hosts(self):
-        return {alloc.host_name for alloc in self.allocations.all()}
+    def short_host_names(self):
+        return {alloc.host_name.split('.', 1)[0] for alloc in self.allocations.all()}
+
+    @property
+    def terminated(self):
+        return self.get_status_display() in [TaskStatus.FINISHED, TaskStatus.FAILED, TaskStatus.CANCELED]
 
 
 class Allocation(models.Model):
 
 
 class Allocation(models.Model):
+    STATUS_CHOICES = list(enumerate(field for field in dir(AllocationType) if not field.startswith('__')))
+    STATUS_CHOICES_REVERSED = {v: k for k, v in STATUS_CHOICES}
+
     task = models.ForeignKey(Task, verbose_name='Zadanie', related_name='allocations')
 
     host_name = models.CharField(u"Host", max_length=100)
     task = models.ForeignKey(Task, verbose_name='Zadanie', related_name='allocations')
 
     host_name = models.CharField(u"Host", max_length=100)
+    status = models.IntegerField(u"Status", choices=STATUS_CHOICES)
     status_description = models.TextField(u"Opis statusu", blank=True, default='')
     status_description = models.TextField(u"Opis statusu", blank=True, default='')
-    processes_count = models.PositiveIntegerField(u"Liczba procesorów")
-    slots_count = models.PositiveIntegerField(u"Liczba slotów")
+    processes_count = models.PositiveIntegerField(u"Liczba procesów")
+    slots_count = models.PositiveIntegerField(u"Liczba rdzeni")
     processes_group_id = models.TextField(u"Identyfikator grupy procesów", blank=True, default='')
     submission_time = models.DateTimeField(u"Data wysłania")
     estimated_start_time = models.DateTimeField(u"Przewidywana data rozpoczęcia", blank=True, null=True)
     processes_group_id = models.TextField(u"Identyfikator grupy procesów", blank=True, default='')
     submission_time = models.DateTimeField(u"Data wysłania")
     estimated_start_time = models.DateTimeField(u"Przewidywana data rozpoczęcia", blank=True, null=True)
@@ -179,6 +199,7 @@ class Allocation(models.Model):
     efficiency = models.IntegerField(u"Efektywność", blank=True, null=True)  # ??
     comment = models.TextField(u"Komentarz", blank=True, default='')
     memory_usage = models.PositiveIntegerField(u"Użycie pamięci", blank=True, null=True)
     efficiency = models.IntegerField(u"Efektywność", blank=True, null=True)  # ??
     comment = models.TextField(u"Komentarz", blank=True, default='')
     memory_usage = models.PositiveIntegerField(u"Użycie pamięci", blank=True, null=True)
+    working_directory = models.CharField(u"Katalog roboczy", max_length=1024, blank=True, default='')
 
     class Meta:
         verbose_name = u"Alokacja"
 
     class Meta:
         verbose_name = u"Alokacja"
@@ -190,17 +211,22 @@ class Allocation(models.Model):
 
     @staticmethod
     def qcg_map(qcg_allocation):
 
     @staticmethod
     def qcg_map(qcg_allocation):
-        return get_attributes(qcg_allocation, ('host_name', 'status_description', 'processes_count', 'slots_count',
-                                               'processes_group_id', 'submission_time', 'estimated_start_time',
-                                               'finish_time', 'local_submission_time', 'local_start_time',
-                                               'local_finish_time', 'purged', 'efficiency', 'comment', 'memory_usage'))
+        attrs = get_attributes(qcg_allocation, ('host_name', 'status_description', 'processes_count', 'slots_count',
+                                                'processes_group_id', 'submission_time', 'estimated_start_time',
+                                                'finish_time', 'local_submission_time', 'local_start_time',
+                                                'local_finish_time', 'purged', 'efficiency', 'comment', 'memory_usage',
+                                                'working_directory'))
+
+        attrs['status'] = Allocation.STATUS_CHOICES_REVERSED[qcg_allocation.status]
+
+        return attrs
 
 
 class NodeInfo(models.Model):
     allocation = models.ForeignKey(Allocation, verbose_name=u"Alokacja", related_name='nodes')
 
     name = models.CharField(u"Nazwa", max_length=32)
 
 
 class NodeInfo(models.Model):
     allocation = models.ForeignKey(Allocation, verbose_name=u"Alokacja", related_name='nodes')
 
     name = models.CharField(u"Nazwa", max_length=32)
-    count = models.PositiveSmallIntegerField(u"Liczba slotów", blank=True, null=True)
+    count = models.PositiveSmallIntegerField(u"Liczba rdzeni", blank=True, null=True)
 
     class Meta:
         verbose_name = u"Węzeł"
 
     class Meta:
         verbose_name = u"Węzeł"
@@ -216,6 +242,23 @@ class NodeInfo(models.Model):
 
     @staticmethod
     def qcg_map(qcg_node):
 
     @staticmethod
     def qcg_map(qcg_node):
-        return {'name': qcg_node.value,
-                # FIXME
-                'count': qcg_node.slots_count.intValue() if qcg_node.slots_count is not None else None}
+        return {'name': qcg_node.name, 'count': qcg_node.slots_count}
+
+
+class JobTemplate(models.Model):
+    name = models.CharField(u"Nazwa", max_length=100)
+    description = models.TextField(u"Opis")
+
+    owner = models.ForeignKey(User, verbose_name=u"Właściciel", related_name='templates')
+    created = models.DateTimeField(u"Utworzono", auto_now_add=True)
+    updated = models.DateTimeField(u"Uaktualniono", auto_now=True)
+
+    class Meta:
+        verbose_name = u"Szablon zadania"
+        verbose_name_plural = u"Szablony zadania"
+
+    def __unicode__(self):
+        return u"{} ({})".format(self.name, self.owner)
+
+    def get_absolute_url(self):
+        return reverse('template_submit', kwargs={'template_id': self.id})