# 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
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('__')))
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')
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:
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:
{% 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 »</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 %}
{% 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>
--- /dev/null
+{% 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 %}
--- /dev/null
+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)
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'),
)
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
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')
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
# 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})