=== modified file '.bzrignore'
@@ -4,3 +4,4 @@
./_trial_temp
./twisted/plugins/dropin.cache
./dist
+./build
=== modified file 'lava_scheduler_app/extension.py'
@@ -61,3 +61,7 @@
import lava_scheduler_app
return versiontools.format_version(
lava_scheduler_app.__version__, lava_scheduler_app)
+
+ def contribute_to_settings(self, settings_module):
+ super(SchedulerExtension, self).contribute_to_settings(settings_module)
+ settings_module['INSTALLED_APPS'].append('django_tables2')
=== added file 'lava_scheduler_app/tables.py'
@@ -0,0 +1,85 @@
+import simplejson
+
+import django_tables2 as tables
+from django_tables2.rows import BoundRow
+from django_tables2.utils import AttributeDict
+
+from lava.utils.data_tables.views import DataTableView
+from lava.utils.data_tables.backends import QuerySetBackend
+
+
+class AjaxColumn(tables.Column):
+
+ def __init__(self, *args, **kw):
+ sort_expr = kw.pop('sort_expr', None)
+ width = kw.pop('width', None)
+ super(AjaxColumn, self).__init__(*args, **kw)
+ self.sort_expr = sort_expr
+ self.width = width
+
+
+class _ColWrapper(object):
+
+ def __init__(self, name, sort_expr, table):
+ self.name = name
+ if sort_expr is not None:
+ self.sort_expr = sort_expr
+ else:
+ self.sort_expr = name
+ self.table = table
+
+ def callback(self, record):
+ # It _might_ make life more convenient to handle certain non-JSONable
+ # datatypes here -- particularly, applying unicode() to model objects
+ # would be more consistent with the way templates work.
+ return BoundRow(self.table, record)[self.name]
+
+
+class AjaxTable(tables.Table):
+ datatable_opts = None
+ searchable_columns = []
+
+ def __init__(self, id, source, **kw):
+ if 'template' not in kw:
+ kw['template'] = 'lava_scheduler_app/ajax_table.html'
+ super(AjaxTable, self).__init__(data=[], **kw)
+ self.source = source
+ self.attrs = AttributeDict({
+ 'id': id,
+ 'class': 'display',
+ })
+
+ @classmethod
+ def json(cls, request, queryset):
+ table = cls(None, None)
+ our_cols = [_ColWrapper(name, col.sort_expr, table)
+ for name, col in cls.base_columns.iteritems()]
+ return DataTableView.as_view(
+ backend=QuerySetBackend(
+ queryset=queryset,
+ columns=our_cols,
+ searching_columns=cls.searchable_columns)
+ )(request)
+
+ def datatable_options(self):
+ if self.datatable_opts:
+ opts = self.datatable_opts.copy()
+ else:
+ opts = {}
+ opts.update({
+ 'bJQueryUI': True,
+ 'bServerSide': True,
+ 'bProcessing': True,
+ 'sAjaxSource': self.source,
+ 'bFilter': bool(self.searchable_columns)
+ })
+ aoColumnDefs = opts['aoColumnDefs'] = []
+ for col in self.columns:
+ aoColumnDefs.append({
+ 'bSortable': bool(col.sortable),
+ 'mDataProp': col.name,
+ 'aTargets': [col.name],
+ })
+ if col.column.width:
+ aoColumnDefs[-1]['sWidth'] = col.column.width
+ return simplejson.dumps(opts)
=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/_content.html'
@@ -3,23 +3,6 @@
{% block extrahead %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava-server/css/demo_table_jui.css"/>
-<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/FixedHeader.min.js"></script>
-<script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.dataTables.min.js"></script>
-<script type="text/javascript">
-jQuery.fn.dataTableExt.oSort['num-html-asc'] = function(a,b) {
- var x = a.replace( /<.*?>/g, "" );
- var y = b.replace( /<.*?>/g, "" );
- x = parseFloat( x );
- y = parseFloat( y );
- return ((x < y) ? -1 : ((x > y) ? 1 : 0));
-};
-
-jQuery.fn.dataTableExt.oSort['num-html-desc'] = function(a,b) {
- var x = a.replace( /<.*?>/g, "" );
- var y = b.replace( /<.*?>/g, "" );
- x = parseFloat( x );
- y = parseFloat( y );
- return ((x < y) ? 1 : ((x > y) ? -1 : 0));
-};
-</script>
+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/FixedHeader.min.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.dataTables.min.js"></script>
{% endblock %}
=== added file 'lava_scheduler_app/templates/lava_scheduler_app/ajax_table.html'
@@ -0,0 +1,25 @@
+{% extends "django_tables2/table.html" %}
+
+{% block table.thead %}
+<thead>
+ <tr>
+ {% for column in table.columns %}
+ <th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
+ {% endfor %}
+ </tr>
+</thead>
+{% endblock table.thead %}
+
+{% block table %}
+{{ block.super }}
+{% comment %}
+The div below is a ridiculous hack to avoid the jQuery.details.js
+setting the display: of the script element to "block" when showing a
+table that is in a <details> element.
+{% endcomment %}
+<div>
+<script type="text/javascript">
+$("#{{ table.attrs.id }}").dataTable({{ table.datatable_options|safe }});
+</script>
+</div>
+{% endblock %}
=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/alljobs.html'
@@ -1,62 +1,10 @@
{% extends "lava_scheduler_app/_content.html" %}
+{% load django_tables2 %}
{% block content %}
<h2>All Jobs</h2>
-<table class="data display">
- <thead>
- <tr>
- <th class="id">ID</th>
- <th class="status">Status</th>
- <th class="device">Device</th>
- <th class="description">Description</th>
- <th class="submitter">Submitter</th>
- <th class="submit_time">Submit Time</th>
- </tr>
- </thead>
- <tbody>
- </tbody>
-</table>
-
-<script>
-$(document).ready(
- function() {
- $("table.data").dataTable({
- bJQueryUI: true,
- bServerSide: true,
- bProcessing: true,
- bFilter: true,
- sAjaxSource: "{% url lava_scheduler_app.views.alljobs_json %}",
- aaSorting: [[0, "desc"]],
- iDisplayLength: 25,
- aoColumnDefs: [
- {
- aTargets: ["id"],
- fnRender: function (o) { return '<a href="' + o.aData.id.link + '">' + o.aData.id.id + '</a>'; }
- },
- {aTargets: ["status"], mDataProp: 'status'},
- {aTargets: ["description"], mDataProp: 'description'},
- {aTargets: ["submitter"], mDataProp: 'submitter'},
- {aTargets: ["submit_time"], mDataProp: 'submit_time'},
- {
- aTargets: ["device"],
- fnRender: function (o) {
- o = o.aData.device;
- r = o.name;
- if (o.requested) {
- r = '<i>' + r + '</i>';
- }
- if (o.link) {
- return '<a href="' + o.link + '">' + r + '</a>';
- }
- return r;
- }
- }
- ]
- });
- }
-);
-</script>
+{% render_table alljobs_table %}
{% endblock %}
=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/device.html'
@@ -1,5 +1,7 @@
{% extends "lava_scheduler_app/_content.html" %}
+{% load django_tables2 %}
+
{% block extrahead %}
{{ block.super }}
<style type="text/css">
@@ -89,79 +91,18 @@
<div style="clear: both"></div>
</div>
-<table class="jobs display">
- <thead>
- <tr>
- <th class="id">ID</th>
- <th>Status</th>
- <th>Device</th>
- <th>Submitter</th>
- <th>Start Time</th>
- <th>Finish Time</th>
- </tr>
- </thead>
- <tbody>
- {% for job in recent_job_list %}
- <tr>
- <td><a href="{{ job.get_absolute_url }}">{{ job.id }}</a></td>
- <td>{{ job.get_status_display }}</td>
- <td>{{ job.actual_device|default:'' }}</td>
- <td>{{ job.submitter }}</td>
- <td>{{ job.start_time }}</td>
- <td>{{ job.end_time|default:"not finished" }}</td>
- </tr>
- {% endfor %}
- </tbody>
-</table>
+{% render_table recent_job_table %}
<details>
<summary>See status transitions</summary>
- <table class="transitions display">
- <thead>
- <tr>
- <th>When</th>
- <th>Transition</th>
- <th>By</th>
- <th>Reason</th>
- </tr>
- </thead>
- <tbody>
- {% for tr in transition_list %}
- <tr>
- <td>
- {{ tr.0|date:"Y-m-d H:i" }}
- {% if tr.1 %}
- (after {{ tr.1|timesince:tr.0 }})
- {% endif %}
- </td>
- <td>{{ tr.2 }} → {{ tr.3 }}</td>
- <td>{{ tr.4 }}</td>
- <td>
- {% if tr.5 %}
- {{ tr.5 }}
- {% endif %}
- </td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
+ {% render_table transition_table %}
</details>
<script>
$(document).ready(
function() {
$('html').addClass($.fn.details.support ? 'details' : 'no-details');
$('details').details();
- $("table.jobs").dataTable({
- "bJQueryUI": true,
- "aoColumnDefs": [
- { "sType": "num-html", "aTargets": [ "id" ] }
- ],
- "aaSorting": [[4, 'desc', 4]]
- });
- $("table.transitions").dataTable({
- "bJQueryUI": true,
- "bSort": false
- });
+ $('script').css('visibility', 'hidden');
{% if show_maintenance %}
$("#maintenance-button").button();
$("#maintenance-button").click(function (e) {
=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/health_jobs.html'
@@ -1,5 +1,7 @@
{% extends "lava_scheduler_app/_content.html" %}
+{% load django_tables2 %}
+
{% block extrahead %}
{{ block.super }}
<style type="text/css">
@@ -61,45 +63,7 @@
<div style="clear: both"></div>
</div>
-<table class="data display">
- <thead>
- <tr>
- <th class="id">ID</th>
- <th>Status</th>
- <th>Device</th>
- <th>Submitter</th>
- <th>Start Time</th>
- <th>Finish Time</th>
- </tr>
- </thead>
- <tbody>
- {% for job in recent_job_list %}
- <tr>
- <td><a href="{{ job.get_absolute_url }}">{{ job.id }}</a></td>
- <td>{{ job.get_status_display }}</td>
- <td>{{ job.actual_device|default:'' }}</td>
- <td>{{ job.submitter }}</td>
- <td>{{ job.start_time }}</td>
- <td>{{ job.end_time|default:"not finished" }}</td>
- </tr>
- {% endfor %}
- </tbody>
-</table>
-<script>
-$(document).ready(
- function() {
- $("table.data").dataTable({
- "bJQueryUI": true,
- "aoColumnDefs": [
- { "sType": "num-html", "aTargets": [ "id" ] }
- ],
- "aaSorting": [[4, 'desc', 4]]
- });
- $("#maintenance-button").button();
- $("#online-button").button();
- }
-);
-</script>
+{% render_table health_job_table %}
{% endblock %}
=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/index.html'
@@ -1,86 +1,18 @@
{% extends "lava_scheduler_app/_content.html" %}
+{% load django_tables2 %}
+
{% block content %}
<h2>Devices</h2>
-<table class="display data device">
- <thead>
- <tr>
- <th>Hostname</th>
- <th>Type</th>
- <th>Status</th>
- <th>Health Status</th>
- </tr>
- </thead>
- <tbody>
- {% for device in devices %}
- <tr>
- <td>
- <a href="{{ device.get_absolute_url }}">{{ device.hostname }}</a>
- </td>
- <td>{{ device.device_type }}</td>
- <td>{{ device.get_status_display }}</td>
- <td>{{ device.get_health_status_display }}</td>
- </tr>
- {% endfor %}
- </tbody>
-</table>
+{% render_table devices_table %}
<a href="{% url lava.scheduler.labhealth %}">All Devices Health</a>
<h2>Active Jobs</h2>
-<table class="data display job">
- <thead>
- <tr>
- <th class="id">ID</th>
- <th>Status</th>
- <th>Device</th>
- <th>Description</th>
- <th>Submitter</th>
- <th>Submit Time</th>
- </tr>
- </thead>
- <tbody>
- {% for job in jobs %}
- <tr>
- <td><a href="{{ job.get_absolute_url }}">{{ job.id }}</a></td>
- <td>{{ job.get_status_display }}</td>
- {% if job.actual_device %}
- <td><a href="{{ job.actual_device.get_absolute_url }}"
- >{{ job.actual_device }}</a></td>
- {% else %}{% if job.requested_device %}
- <td><a href="{{ job.requested_device.get_absolute_url }}"
- ><i>{{ job.requested_device }}</i></a></td>
- {% else %}
- <td><i>{{ job.requested_device_type|default:'' }}</i></td>
- {% endif %}{% endif %}
- <td>{{ job.description|default:'' }}</td>
- <td>{{ job.submitter }}</td>
- <td>{{ job.submit_time }}</td>
- </tr>
- {% endfor %}
- </tbody>
-</table>
+{% render_table active_jobs_table %}
<a href="{% url lava.scheduler.job.list %}">All jobs</a>
-<script>
-$(document).ready(
- function() {
- $("table.data.device").dataTable({
- "bJQueryUI": true,
- "aaSorting": [[0, "asc"]]
- });
- $("table.data.job").dataTable({
- "bJQueryUI": true,
- "aoColumnDefs": [
- { "sType": "num-html", "aTargets": [ "id" ] }
- ],
- "aaSorting": [[0, "asc"]]
- });
- }
-);
-</script>
-
{% endblock %}
=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/labhealth.html'
@@ -1,43 +1,10 @@
{% extends "lava_scheduler_app/_content.html" %}
+{% load django_tables2 %}
{% block content %}
<h2>Lab Health</h2>
-<table class="data display">
- <thead>
- <tr>
- <th class="device">Hostname</th>
- <th>Health Status</th>
- <th>Last Report Time</th>
- <th>Last Report Job</th>
- </tr>
- </thead>
- <tbody>
- {% for device_health in device_health_list %}
- <tr>
- <td><a href="{{ device_health.get_device_health_url }}">{{ device_health.hostname }}</a></td>
- <td>{{ device_health.get_health_status_display }}</td>
- <td>{{ device_health.last_health_report_job.end_time }}</a></td>
- <td><a href="{{ device_health.last_health_report_job.get_absolute_url }}">{{ device_health.last_health_report_job.id }}</a></td>
- </tr>
- {% endfor %}
- </tbody>
-</table>
-
-<script>
-$(document).ready(
- function() {
- $("table.data").dataTable({
- "bJQueryUI": true,
- "aoColumnDefs": [
- { "sType": "num-html", "aTargets": [ "device" ] }
- ],
- "aaSorting": [[0, "desc"]],
- "iDisplayLength": 25
- });
- }
-);
-</script>
+{% render_table device_health_table %}
{% endblock %}
=== modified file 'lava_scheduler_app/urls.py'
@@ -6,6 +6,12 @@
url(r'^$',
'index',
name='lava.scheduler'),
+ url(r'^active_jobs_json$',
+ 'index_active_jobs_json',
+ name='lava.scheduler'),
+ url(r'^devices_json$',
+ 'index_devices_json',
+ name='lava.scheduler'),
url(r'^alljobs$',
'job_list',
name='lava.scheduler.job.list'),
@@ -15,6 +21,12 @@
url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)$',
'device_detail',
name='lava.scheduler.device.detail'),
+ url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/recent_jobs_json$',
+ 'recent_jobs_json',
+ name='lava.scheduler.device.recent_jobs_json'),
+ url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/transition_json$',
+ 'transition_json',
+ name='lava.scheduler.device.transition_json'),
url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/maintenance$',
'device_maintenance_mode',
name='lava.scheduler.device.maintenance'),
@@ -24,9 +36,15 @@
url(r'^labhealth/$',
'lab_health',
name='lava.scheduler.labhealth'),
+ url(r'^labhealth/health_json$',
+ 'lab_health_json',
+ name='lava.scheduler.labhealth_json'),
url(r'^labhealth/device/(?P<pk>[-_a-zA-Z0-9]+)$',
'health_job_list',
name='lava.scheduler.labhealth.detail'),
+ url(r'^labhealth/device/(?P<pk>[-_a-zA-Z0-9]+)/job_json$',
+ 'health_jobs_json',
+ name='lava.scheduler.labhealth.health_jobs_json'),
url(r'^job/(?P<pk>[0-9]+)$',
'job_detail',
name='lava.scheduler.job.detail'),
=== modified file 'lava_scheduler_app/views.py'
@@ -20,9 +20,6 @@
from django.template import RequestContext
from django.template import defaultfilters as filters
-from lava.utils.data_tables.views import DataTableView
-from lava.utils.data_tables.backends import QuerySetBackend, Column
-
from lava_server.views import index as lava_index
from lava_server.bread_crumbs import (
BreadCrumb,
@@ -39,6 +36,10 @@
DeviceStateTransition,
TestJob,
)
+from lava_scheduler_app.tables import (
+ AjaxColumn,
+ AjaxTable,
+ )
@@ -50,61 +51,167 @@
return decorated
+class DateColumn(AjaxColumn):
+
+ def __init__(self, **kw):
+ self._format = kw.get('date_format', settings.DATETIME_FORMAT)
+ super(DateColumn, self).__init__(**kw)
+
+ def render(self, value):
+ return filters.date(value, self._format)
+
+
+class IDLinkColumn(AjaxColumn):
+
+ def __init__(self, verbose_name="ID", **kw):
+ kw['verbose_name'] = verbose_name
+ super(IDLinkColumn, self).__init__(**kw)
+
+ def render(self, record):
+ return '<a href="%s">%s</a>' % (record.get_absolute_url(), record.pk)
+
+
+def all_jobs_with_device_sort():
+ return TestJob.objects.select_related(
+ "actual_device", "requested_device", "requested_device_type",
+ "submitter").extra(
+ select={
+ 'device_sort': 'coalesce(actual_device_id, requested_device_id, requested_device_type_id)'
+ }).all()
+
+
+class JobTable(AjaxTable):
+
+ def render_device(self, record):
+ if record.actual_device:
+ return '<a href="%s">%s</a>' % (
+ record.actual_device.get_absolute_url(), record.actual_device.pk)
+ elif record.requested_device:
+ return '<a href="%s">%s</a>' % (
+ record.requested_device.get_absolute_url(), record.requested_device.pk)
+ else:
+ return '<i>' + record.requested_device_type.pk + '</i>'
+
+ id = IDLinkColumn()
+ status = AjaxColumn()
+ device = AjaxColumn(sort_expr='device_sort')
+ description = AjaxColumn(width="30%")
+ submitter = AjaxColumn(accessor='submitter.username')
+ submit_time = DateColumn()
+ end_time = DateColumn()
+
+ datatable_opts = {
+ 'aaSorting': [[0, 'desc']],
+ }
+ searchable_columns=['description']
+
+
+class IndexJobTable(JobTable):
+ class Meta:
+ exclude = ('end_time',)
+
+
+def index_active_jobs_json(request):
+ return IndexJobTable.json(
+ request, all_jobs_with_device_sort().filter(
+ status__in=[TestJob.SUBMITTED, TestJob.RUNNING]))
+
+
+class DeviceTable(AjaxTable):
+
+ hostname = IDLinkColumn("hostname")
+ device_type = AjaxColumn(accessor='device_type.pk')
+ status = AjaxColumn()
+ health_status = AjaxColumn()
+
+ searchable_columns=['hostname']
+
+
+def index_devices_json(request):
+ return DeviceTable.json(
+ request, Device.objects.select_related("device_type"))
+
+
@BreadCrumb("Scheduler", parent=lava_index)
def index(request):
return render_to_response(
"lava_scheduler_app/index.html",
{
- 'devices': Device.objects.select_related("device_type"),
- 'jobs': TestJob.objects.select_related(
- "actual_device", "requested_device", "requested_device_type",
- "submitter").filter(status__in=[
- TestJob.SUBMITTED, TestJob.RUNNING]),
+ 'devices_table': DeviceTable('devices', reverse(index_devices_json)),
+ 'active_jobs_table': IndexJobTable(
+ 'active_jobs', reverse(index_active_jobs_json)),
'bread_crumb_trail': BreadCrumbTrail.leading_to(index),
},
RequestContext(request))
-@BreadCrumb("All Jobs", parent=index)
-def job_list(request):
- return render_to_response(
- "lava_scheduler_app/alljobs.html",
- {
- 'bread_crumb_trail': BreadCrumbTrail.leading_to(job_list),
- },
- RequestContext(request))
+class DeviceHealthTable(AjaxTable):
+
+ def render_hostname(self, record):
+ return '<a href="%s">%s</a>' % (record.get_device_health_url(), record.pk)
+
+ def render_last_report_job(self, record):
+ report = record.last_health_report_job
+ if report is None:
+ return ''
+ else:
+ return '<a href="%s">%s</a>' % (report.get_absolute_url(), report.pk)
+
+ hostname = AjaxColumn("hostname")
+ health_status = AjaxColumn()
+ last_report_time = DateColumn(accessor="last_health_report_job.end_time")
+ last_report_job = AjaxColumn()
+
+ searchable_columns=['hostname']
+ datatable_opts = {
+ "iDisplayLength": 25
+ }
+
+
+def lab_health_json(request):
+ return DeviceHealthTable.json(
+ request, Device.objects.select_related(
+ "hostname", "last_health_report_job"))
+
@BreadCrumb("All Device Health", parent=index)
def lab_health(request):
- device_health_list = Device.objects.select_related(
- "hostname", "health_status").all()
return render_to_response(
"lava_scheduler_app/labhealth.html",
{
- 'device_health_list': device_health_list,
+ 'device_health_table': DeviceHealthTable(
+ 'device_health', reverse(lab_health_json)),
'bread_crumb_trail': BreadCrumbTrail.leading_to(lab_health),
},
RequestContext(request))
+
+class HealthJobTable(JobTable):
+ class Meta:
+ exclude = ('description', 'device')
+
+
+
+def health_jobs_json(request, pk):
+ device = get_object_or_404(Device, pk=pk)
+ return HealthJobTable.json(
+ request, TestJob.objects.select_related(
+ "submitter",
+ ).filter(
+ actual_device=device,
+ health_check=True))
+
+
@BreadCrumb("All Health Jobs on Device {pk}", parent=index, needs=['pk'])
def health_job_list(request, pk):
device = get_object_or_404(Device, pk=pk)
- recent_health_jobs = TestJob.objects.select_related(
- "actual_device",
- "health_check",
- "end_time",
- ).filter(
- actual_device=device,
- health_check=True
- ).order_by(
- '-end_time'
- )
return render_to_response(
"lava_scheduler_app/health_jobs.html",
{
'device': device,
- 'recent_job_list': recent_health_jobs,
+ 'health_job_table': HealthJobTable(
+ 'health_jobs', reverse(health_jobs_json, kwargs=dict(pk=pk))),
'show_maintenance': device.can_admin(request.user) and \
device.status in [Device.IDLE, Device.RUNNING],
'show_online': device.can_admin(request.user) and \
@@ -113,51 +220,30 @@
},
RequestContext(request))
-def device_callback(job):
- if job.actual_device:
- return dict(
- name=job.actual_device.pk, requested=False,
- link=reverse(device_detail, kwargs=dict(pk=job.actual_device.pk)))
- elif job.requested_device:
- return dict(
- name=job.requested_device.pk, requested=True,
- link=reverse(device_detail, kwargs=dict(pk=job.requested_device.pk)))
- else:
- return dict(name=job.requested_device_type.pk, requested=True)
-
-
-def id_callback(job):
- if job is None:
- return job
- else:
- return dict(id=job.id, link=reverse(job_detail, kwargs=dict(pk=job.id)))
-
-
-alljobs_json = DataTableView.as_view(
- backend=QuerySetBackend(
- queryset=TestJob.objects.select_related(
- "actual_device", "requested_device", "requested_device_type",
- "submitter").extra(
- select={
- 'device_sort': 'coalesce(actual_device_id, requested_device_id, requested_device_type_id)'
- }).all(),
- columns=[
- Column(
- 'id', 'id', id_callback),
- Column(
- 'status', 'status', lambda job: job.get_status_display()),
- Column(
- 'device', 'device_sort', device_callback),
- Column(
- 'description', 'description', lambda job: job.description),
- Column(
- 'submitter', 'submitter', lambda job: job.submitter.username),
- Column(
- 'submit_time', 'submit_time',
- lambda job: filters.date(
- job.submit_time, settings.DATETIME_FORMAT)),
- ],
- searching_columns=['description']))
+
+class AllJobsTable(JobTable):
+
+ datatable_opts = JobTable.datatable_opts.copy()
+
+ datatable_opts.update({
+ 'iDisplayLength': 25,
+ })
+
+
+def alljobs_json(request):
+ return AllJobsTable.json(
+ request, all_jobs_with_device_sort())
+
+
+@BreadCrumb("All Jobs", parent=index)
+def job_list(request):
+ return render_to_response(
+ "lava_scheduler_app/alljobs.html",
+ {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(job_list),
+ 'alljobs_table': AllJobsTable('alljobs', reverse(alljobs_json)),
+ },
+ RequestContext(request))
@BreadCrumb("Job #{pk}", parent=index, needs=['pk'])
@@ -333,6 +419,59 @@
return HttpResponse(json_text, content_type=content_type)
+class RecentJobsTable(JobTable):
+ class Meta:
+ exclude = ('device',)
+
+
+def recent_jobs_json(request, pk):
+ device = get_object_or_404(Device, pk=pk)
+ return RecentJobsTable.json(request, device.recent_jobs())
+
+
+class DeviceTransitionTable(AjaxTable):
+
+ def render_created_on(self, record):
+ t = record
+ base = filters.date(t.created_on, "Y-m-d H:i")
+ if t.prev:
+ base += ' (after %s)' % (filters.timesince(t.prev, t.created_on))
+ return base
+
+ def render_transition(self, record):
+ t = record
+ return '%s → %s' % (t.get_old_state_display(), t.get_new_state_display(),)
+
+ def render_message(self, value):
+ if value is None:
+ return ''
+ else:
+ return value
+
+ created_on = AjaxColumn('when', width="40%")
+ transition = AjaxColumn('transition', sortable=False)
+ created_by = AjaxColumn('by', accessor='created_by.username')
+ message = AjaxColumn('reason')
+
+ datatable_opts = {
+ 'aaSorting': [[0, 'desc']],
+ }
+
+
+def transition_json(request, pk):
+ device = get_object_or_404(Device, pk=pk)
+ qs = device.transitions.select_related('created_by')
+ qs = qs.extra(select={'prev': """
+ select t.created_on
+ from lava_scheduler_app_devicestatetransition as t
+ where t.device_id=%s and t.created_on < lava_scheduler_app_devicestatetransition.created_on
+ order by t.created_on desc
+ limit 1 """},
+ select_params=[device.pk])
+ return DeviceTransitionTable.json(request, qs)
+
+
+
@BreadCrumb("Device {pk}", parent=index, needs=['pk'])
def device_detail(request, pk):
device = get_object_or_404(Device, pk=pk)
@@ -343,26 +482,15 @@
transition = None
else:
transition = None
- transition_models = device.transitions.order_by('created_on').select_related('created_by')
- transition_list = []
- if transition_models:
- for i, t in enumerate(transition_models):
- if i > 0:
- before = transition_models[i-1].created_on
- else:
- before = None
- transition_list.append(
- (t.created_on, before,
- t.get_old_state_display(), t.get_new_state_display(),
- t.created_by, t.message))
- transition_list.reverse()
return render_to_response(
"lava_scheduler_app/device.html",
{
'device': device,
'transition': transition,
- 'transition_list': transition_list,
- 'recent_job_list': device.recent_jobs,
+ 'transition_table': DeviceTransitionTable(
+ 'transitions', reverse(transition_json, kwargs=dict(pk=device.pk))),
+ 'recent_job_table': RecentJobsTable(
+ 'jobs', reverse(recent_jobs_json, kwargs=dict(pk=device.pk))),
'show_maintenance': device.can_admin(request.user) and \
device.status in [Device.IDLE, Device.RUNNING],
'show_online': device.can_admin(request.user) and \
=== modified file 'setup.py'
@@ -33,6 +33,7 @@
scheduler = lava_scheduler_app.extension:SchedulerExtension
""",
install_requires=[
+ "django-tables2",
"lava-server >= 0.10",
"simplejson",
"south >= 0.7.3",