job and task details view
authorMaciej Tronowski <mtro@man.poznan.pl>
Fri, 20 Feb 2015 15:22:38 +0000 (16:22 +0100)
committerMaciej Tronowski <mtro@man.poznan.pl>
Fri, 20 Feb 2015 15:22:38 +0000 (16:22 +0100)
qcg/models.py
qcg/templates/qcg/job.html
qcg/templates/qcg/jobs.html
qcg/templates/qcg/task.html [new file with mode: 0644]
qcg/templatetags/__init__.py [new file with mode: 0644]
qcg/templatetags/qcg_utils.py [new file with mode: 0644]
qcg/urls.py
qcg/utils.py
qcg/views.py

index b9a35e5..6c25e3b 100644 (file)
@@ -1,5 +1,6 @@
 # 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
@@ -15,6 +16,10 @@ __all__ = ['User', 'Job', 'Task', 'Allocation', 'NodeInfo']
 class User(AbstractUser):
     last_update = models.DateTimeField(default=now)
 
+    @property
+    def tasks(self):
+        return Task.objects.filter(job__owner=self)
+
 
 class Job(models.Model):
     STATUS_CHOICES = list(enumerate(field for field in dir(JobStatus) if not field.startswith('__')))
@@ -27,10 +32,10 @@ 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='')
+    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)
     proxy_lifetime = TimedeltaField(u"Czas życia proxy", blank=True, null=True)
-    project = models.TextField(u"Projekt", blank=True, default='')
     purged = models.BooleanField(u"Usunięty katalog roboczy?", default=False)
 
     owner = models.ForeignKey(User, verbose_name=u"Właściciel", related_name='jobs')
@@ -45,6 +50,9 @@ class Job(models.Model):
     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:
@@ -115,6 +123,9 @@ class Task(models.Model):
     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):
         if self._qcg_task is None:
index 3a0f787..cb8b687 100644 (file)
@@ -1,53 +1,64 @@
 {% extends 'qcg/base.html' %}
+{% load qcg_utils %}
 
 {% block container %}
-    <h1 class="page-header">{% block title %}Zadanie J1372950219798__8575{% endblock %}</h1>
+    <ol class="breadcrumb">
+        <li><a href="{% url 'jobs' %}">Zadania</a></li>
+        <li class="active">{{ job.job_id }}</li>
+    </ol>
 
-    <h2>Szczegóły</h2>
+    <h1 class="page-header">{% block title %}Job {{ job.job_id }}{% endblock %}</h1>
 
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">Note</div>
-        <div class="col-sm-10">urban</div>
-    </div>
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">TaskType</div>
-        <div class="col-sm-10">SINGLE</div>
-    </div>
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">SubmissionTime</div>
-        <div class="col-sm-10">Fri Jul 19 11:54:20 CEST 2013</div>
-    </div>
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">FinishTime</div>
-        <div class="col-sm-10">Fri Jul 19 12:02:33 CEST 2013</div>
-    </div>
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">Status</div>
-        <div class="col-sm-10">FINISHED</div>
-    </div>
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">Purged</div>
-        <div class="col-sm-10">false</div>
-    </div>
+    <div role="tabpanel">
+        <!-- Nav tabs -->
+        <ul class="nav nav-tabs" style="margin-bottom: 20px">
+            <li role="presentation" class="active"><a href="#details" data-toggle="tab">Właściwości</a></li>
+            <li role="presentation"><a href="#desc" data-toggle="tab">Opis</a></li>
+        </ul>
 
+        <!-- Tab panes -->
+        <div class="tab-content">
+            <div role="tabpanel" class="tab-pane active" id="details">
+                <h3>Atrybuty</h3>
 
-    <h2>Alokacja</h2>
+                <p>
+                    {% display_attribute 'Status' job.get_status_display %}
+                    {% display_attribute 'Notatka' job.note %}
+                    {% display_attribute 'Projekt' job.project %}
+                    {% display_attribute 'Czas życia proxy' job.proxy_lifetime %}
+                    {% display_attribute 'Data zlecenia' job.submission_time %}
+                    {% display_attribute 'Data zakończenia' job.finish_time %}
+                    {% display_attribute 'Usunięty katalog roboczy?' job.purged %}
+                </p>
 
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">HostName</div>
-        <div class="col-sm-10">hydra.icm.edu.pl</div>
-    </div>
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">ProcessesCount</div>
-        <div class="col-sm-10">16</div>
-    </div>
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">SubmissionTime</div>
-        <div class="col-sm-10">Fri Jul 19 11:54:20 CEST 2013</div>
-    </div>
-    <div class="row">
-        <div class="col-sm-2 text-right text-muted">FinishTime</div>
-        <div class="col-sm-10">Fri Jul 19 12:02:33 CEST 2013</div>
+                <h3>Taski</h3>
+
+                {% if job.tasks.exists %}
+                    <ul class="list-group">
+                        {% for task in job.tasks.all %}
+                            <li class="list-group-item">
+                                {% display_attribute 'Status' task.get_status_display %}
+                                {% display_attribute 'Opis statusu' task.status_description %}
+                                {% display_attribute 'Data rozpoczęcia' task.start_time %}
+                                {% display_attribute 'Data zakończenia' task.finish_time %}
+
+                                <div style="padding: 10px 0 0 50px">
+                                    <a href="{{ task.get_absolute_url }}">więcej&nbsp;&raquo;</a>
+                                </div>
+                            </li>
+                        {% endfor %}
+                    </ul>
+                {% else %}
+                    <div class="panel-body">
+                        <div class="alert alert-info">Brak elementów</div>
+                    </div>
+                {% endif %}
+
+            </div>
+            <div role="tabpanel" class="tab-pane" id="desc">
+                <pre>{{ job.description }}</pre>
+            </div>
+        </div>
     </div>
 
 {% endblock container %}
index 3bb81a0..b599429 100644 (file)
         {% block title %}Lista zadań{% endblock %}
     </h1>
 
-    <form class="row form-inline">
-        <div class="col-md-9 col-md-offset-1">
-            <div class="form-group">
-                <label class="sr-only" for="search">Szukaj w opisie lub uwagach</label>
-                <input type="text" class="form-control" id="search" placeholder="Szukaj w opisie lub uwagach">
-            </div>
-            <div class="form-group">
-                <label class="sr-only" for="status">Status</label>
-                <select class="form-control" id="status">
-                  <option>Wybierz stan</option>
-                  <option>PENDING</option>
-                  <option>RUNNING</option>
-                  <option>FAILED</option>
-                  <option>FINISHED</option>
-                </select>
-            </div>
-            <div class="form-group">
-                <label class="sr-only" for="cluster">Klaster</label>
-                <select class="form-control" id="cluster">
-                  <option>Wybierz klaster</option>
-                  <option>Inula</option>
-                  <option>Galera</option>
-                  <option>Hydra</option>
-                  <option>Zeus</option>
-                </select>
-            </div>
-        </div>
-        <div class="col-md-1">
-            <button type="submit" class="btn btn-default">Filtruj</button>
-        </div>
-    </form>
-
-    <hr />
+{#    <form class="row form-inline">#}
+{#        <div class="col-md-9 col-md-offset-1">#}
+{#            <div class="form-group">#}
+{#                <label class="sr-only" for="search">Szukaj w opisie lub uwagach</label>#}
+{#                <input type="text" class="form-control" id="search" placeholder="Szukaj w opisie lub uwagach">#}
+{#            </div>#}
+{#            <div class="form-group">#}
+{#                <label class="sr-only" for="status">Status</label>#}
+{#                <select class="form-control" id="status">#}
+{#                  <option>Wybierz stan</option>#}
+{#                  <option>PENDING</option>#}
+{#                  <option>RUNNING</option>#}
+{#                  <option>FAILED</option>#}
+{#                  <option>FINISHED</option>#}
+{#                </select>#}
+{#            </div>#}
+{#            <div class="form-group">#}
+{#                <label class="sr-only" for="cluster">Klaster</label>#}
+{#                <select class="form-control" id="cluster">#}
+{#                  <option>Wybierz klaster</option>#}
+{#                  <option>Inula</option>#}
+{#                  <option>Galera</option>#}
+{#                  <option>Hydra</option>#}
+{#                  <option>Zeus</option>#}
+{#                </select>#}
+{#            </div>#}
+{#        </div>#}
+{#        <div class="col-md-1">#}
+{#            <button type="submit" class="btn btn-default">Filtruj</button>#}
+{#        </div>#}
+{#    </form>#}
+{##}
+{#    <hr />#}
 
 
     <nav class="text-center" style="margin-bottom: 15px">
                     {% with job.list.0 as task %}
                         <tr class="treegrid-{{ forloop.counter }}">
                             <td>
-                                <a href="{% url 'job' %}">
+                                <a href="{{ task.get_absolute_url }}">
                                     <span class="glyphicon glyphicon-file" aria-hidden="true"></span>
 {#                                    {{ task }}#}
                                 </a>
                             </td>
                             <td>{{ task.note }}</td>
-                            <td>{{ task.submission_time }}</td>
+                            <td>{{ task.submission_time|timesince }} temu</td>
                             <td>{{ task.start_time }}</td>
                             <td>{{ task.finish_time }}</td>
                             <td>{{ task.get_status_display }}</td>
                 {% else %}
                     <tr class="treegrid-{{ forloop.counter }}">
                         <td>
-                            <a href="{% url 'job' %}">
+                            <a href="{{ job.grouper.get_absolute_url }}">
                                 <span class="glyphicon glyphicon-folder-close" aria-hidden="true"></span>
 {#                                {{ job.grouper }}#}
                             </a>
                         </td>
                         <td>{{ job.grouper.note }}</td>
-                        <td>{{ job.grouper.submission_time }}</td>
+                        <td>{{ job.grouper.submission_time|timesince  }} temu</td>
                         <td>-</td>
                         <td>{{ job.grouper.finish_time }}</td>
                         <td>{{ job.grouper.get_status_display }}</td>
                     {% for task in job.list %}
                         <tr class="treegrid-{{ forloop.parentloop.counter }}-{{ forloop.counter }} treegrid-parent-{{ forloop.parentloop.counter }}">
                             <td>
-                                <a href="{% url 'job' %}">
+                                <a href="{{ task.get_absolute_url }}">
                                     <span class="glyphicon glyphicon-file" aria-hidden="true"></span>
 {#                                    {{ task }}#}
                                 </a>
                             </td>
                             <td>{{ task.note }}</td>
-                            <td>{{ task.submission_time }}</td>
+                            <td>{{ task.submission_time|timesince  }} temu</td>
                             <td>{{ task.start_time }}</td>
                             <td>{{ task.finish_time }}</td>
                             <td>{{ task.get_status_display }}</td>
diff --git a/qcg/templates/qcg/task.html b/qcg/templates/qcg/task.html
new file mode 100644 (file)
index 0000000..c3ee9a7
--- /dev/null
@@ -0,0 +1,75 @@
+{% extends 'qcg/base.html' %}
+{% load qcg_utils %}
+
+{% block container %}
+    <ol class="breadcrumb">
+        <li><a href="{% url 'jobs' %}">Zadania</a></li>
+        <li><a href="{{ task.job.get_absolute_url }}">{{ task.job.job_id }}</a></li>
+        <li class="active">{{ task.task_id }}</li>
+    </ol>
+
+    <h1 class="page-header">{% block title %}Task {{ task.task_id }}{% endblock %}</h1>
+
+    <div role="tabpanel">
+        <!-- Nav tabs -->
+        <ul class="nav nav-tabs" style="margin-bottom: 20px">
+            <li role="presentation" class="active"><a href="#details" data-toggle="tab">Właściwości</a></li>
+            <li role="presentation"><a href="#desc" data-toggle="tab">Opis</a></li>
+        </ul>
+
+        <!-- Tab panes -->
+        <div class="tab-content">
+            <div role="tabpanel" class="tab-pane active" id="details">
+                <h3>Atrybuty</h3>
+
+                <p>
+                    {% display_attribute 'Status' task.get_status_display %}
+                    {% display_attribute 'Opis statusu' task.status_description %}
+                    {% display_attribute 'Typ' task.get_type_display %}
+                    {% display_attribute 'Notatka' task.note %}
+                    {% display_attribute 'Projekt' task.project %}
+                    {% display_attribute 'Czas życia proxy' task.proxy_lifetime %}
+                    {% display_attribute 'Data zlecenia' task.submission_time %}
+                    {% display_attribute 'Data rozpoczęcia' task.start_time %}
+                    {% display_attribute 'Data zakończenia' task.finish_time %}
+                    {% display_attribute 'Usunięty katalog roboczy?' task.purged %}
+                </p>
+
+                <h3>Alokacje</h3>
+
+                {% if task.allocations.exists %}
+                    <ul class="list-group">
+                        {% for alloc in task.allocations.all %}
+                            <li class="list-group-item">
+                                {% display_attribute 'Komentarz' alloc.comment %}
+                                {% display_attribute 'Host' alloc.host_name %}
+                                {% display_attribute 'Opis statusu' alloc.status_description %}
+                                {% display_attribute 'Liczba procesorów' alloc.processes_count %}
+                                {% display_attribute 'Liczba slotów' alloc.slots_count %}
+                                {% display_attribute 'Identyfikator grupy procesów' alloc.processes_group_id %}
+                                {% display_attribute 'Data zlecenia' task.submission_time %}
+                                {% display_attribute 'Przewidywana data rozpoczęcia' alloc.estimated_start_time %}
+                                {% display_attribute 'Data zakończenia' task.finish_time %}
+                                {% display_attribute 'Lokalna data zlecenia' task.local_submission_time %}
+                                {% display_attribute 'Lokalna data rozpoczęcia' task.local_start_time %}
+                                {% display_attribute 'Lokalna data zakończenia' task.local_finish_time %}
+                                {% display_attribute 'Efektywność' alloc.efficiency %}
+                                {% display_attribute 'Użycie pamięci' alloc.memory_usage %}
+                                {% display_attribute 'Usunięty katalog roboczy?' task.purged %}
+                            </li>
+                        {% endfor %}
+                    </ul>
+                {% else %}
+                    <div class="panel-body">
+                        <div class="alert alert-info">Brak elementów</div>
+                    </div>
+                {% endif %}
+
+            </div>
+            <div role="tabpanel" class="tab-pane" id="desc">
+                <pre>{{ task.description }}</pre>
+            </div>
+        </div>
+    </div>
+
+{% endblock %}
diff --git a/qcg/templatetags/__init__.py b/qcg/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/qcg/templatetags/qcg_utils.py b/qcg/templatetags/qcg_utils.py
new file mode 100644 (file)
index 0000000..33dd748
--- /dev/null
@@ -0,0 +1,29 @@
+from datetime import datetime, timedelta
+
+from django import template
+from django.utils.html import format_html
+from django.utils.timesince import timesince
+from django.utils.timezone import now
+
+from qcg.utils import localtime_str
+
+
+register = template.Library()
+
+
+@register.simple_tag
+def display_attribute(label, value):
+    if isinstance(value, bool):
+        value = 'Tak' if value else 'Nie'
+    elif isinstance(value, datetime):
+        value = localtime_str(value)
+    elif isinstance(value, timedelta):
+        value = timesince(now() - value)
+
+    if not value:
+        return ''
+
+    return format_html(u'<div class="row">'
+                       u'   <div class="col-sm-3 text-right text-muted">{}</div>'
+                       u'   <div class="col-sm-9">{}</div>'
+                       u'</div>', label, value)
index de1a7e3..f265b0f 100644 (file)
@@ -9,5 +9,6 @@ urlpatterns = patterns('',
 
     url(r'^$', views.index, name='index'),
     url(r'^jobs/$', views.jobs_list, name='jobs'),
-    url(r'^job/$', views.job_details, name='job'),
+    url(r'^job/(?P<job_id>[\w]+)/?$', views.job_details, name='job'),
+    url(r'^job/(?P<job_id>[\w]+)/(?P<task_id>[\w]+)/?$', views.task_details, name='task'),
 )
index 76dc318..bc9135f 100644 (file)
@@ -1,6 +1,7 @@
 from django.core.paginator import Paginator
 from django.db import transaction
-from django.utils.timezone import now
+from django.utils.formats import date_format
+from django.utils.timezone import now, localtime
 from pyqcg.service import Registry
 from pyqcg.utils import Credential, TimePeriod
 
@@ -80,3 +81,7 @@ def paginator_context(request, objects, per_page=constants.PER_PAGE):
                         min(max(page_num + 2, 5), paginator.num_pages) + 1)
 
     return {'page': paginator.page(page_num), 'num_pages': paginator.num_pages, 'pages_range': pages_range}
+
+
+def localtime_str(datetime):
+    return date_format(localtime(datetime), 'DATETIME_FORMAT')
index 0590810..9f4a55e 100644 (file)
@@ -3,13 +3,12 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
 from django.contrib.auth.decorators import login_required
 from django.core.urlresolvers import reverse
 from django.http import HttpResponse
-from django.shortcuts import render
+from django.shortcuts import render, get_object_or_404
 from django.utils.http import urlencode
 from django_openid_auth.views import make_consumer
 from openid.extensions import ax
 from pyqcg import QCG
 
-from qcg.models import Task
 from qcg.utils import update_user_data, paginator_context
 
 
@@ -45,14 +44,23 @@ def jobs_list(request):
     # QCG.start()
     # update_user_data(request.user, request.session['proxy'])
 
-    tasks = Task.objects.filter(job__owner=request.user) \
-                        .select_related('job').prefetch_related('allocations__nodes') \
-                        .order_by('-job__submission_time', '-submission_time')
+    tasks = request.user.tasks.order_by('-job__submission_time', '-submission_time') \
+                              .select_related('job').prefetch_related('allocations__nodes')
 
     context = paginator_context(request, tasks)
 
     return render(request, 'qcg/jobs.html', context)
 
 
-def job_details(request):
-    return render(request, 'qcg/job.html')
+@login_required
+def job_details(request, job_id):
+    job = get_object_or_404(request.user.jobs.prefetch_related('tasks'), job_id=job_id)
+
+    return render(request, 'qcg/job.html', {'job': job})
+
+
+def task_details(request, job_id, task_id):
+    task = get_object_or_404(request.user.tasks.select_related('job').prefetch_related('allocations'),
+                             job__job_id=job_id, task_id=task_id)
+
+    return render(request, 'qcg/task.html', {'task': task})