=== modified file 'lava_scheduler_app/tables.py'
@@ -1,48 +1,72 @@
import simplejson
-import django_tables2 as tables
+from django.template import compile_string, RequestContext
+
+from django_tables2.columns import BoundColumn
from django_tables2.rows import BoundRow
+from django_tables2.tables import Table, TableData
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
+simple_nodelist = compile_string('{{ a }}', None)
class _ColWrapper(object):
- def __init__(self, name, sort_expr, table):
+ def __init__(self, name, table):
self.name = name
- if sort_expr is not None:
- self.sort_expr = sort_expr
- else:
- self.sort_expr = name
+ self.sort_expr = table.columns[name].accessor.replace('.', '__')
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):
+ context = self.table.context
+ context.update({"a": BoundRow(self.table, record)[self.name]})
+ try:
+ return simple_nodelist.render(context)
+ finally:
+ context.pop()
+
+
+class _AjaxTableData(TableData):
+ def order_by(self, order_by):
+ if order_by:
+ raise AssertionError(
+ "AjaxTables do not support ordering by Table options")
+ return
+
+
+class AjaxTable(Table):
+ TableDataClass = _AjaxTableData
+
+ def __init__(self, id, source, params=(), _for_rendering=True, **kw):
if 'template' not in kw:
kw['template'] = 'lava_scheduler_app/ajax_table.html'
- super(AjaxTable, self).__init__(data=[], **kw)
+ self.params = params
+ self.total_length = None
+ if _for_rendering:
+ qs = self.get_queryset()
+ self.total_length = qs.count()
+
+ ordering = self.datatable_opts.get('aaSorting', [[0, 'asc']])
+ # What follows is duplicated from backends.py which isn't ideal.
+ order_by = []
+ for column_index, order in ordering:
+ name, col = self.base_columns.items()[column_index]
+ sort_expr = BoundColumn(self, col, name).accessor.replace('.', '__')
+ order_by.append(
+ "{asc_desc}{column}".format(
+ asc_desc="-" if order == 'desc' else '',
+ column=sort_expr))
+ qs = qs.order_by(*order_by)
+
+ display_length = self.datatable_opts.get('iDisplayLength', 10)
+ qs = qs[:display_length]
+ else:
+ qs = []
+ super(AjaxTable, self).__init__(data=qs, **kw)
self.source = source
self.attrs = AttributeDict({
'id': id,
@@ -50,13 +74,13 @@
})
@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()]
+ def json(cls, request, params=()):
+ table = cls(None, None, params, _for_rendering=False)
+ table.context = RequestContext(request)
+ our_cols = [_ColWrapper(name, table) for name in table.columns]
return DataTableView.as_view(
backend=QuerySetBackend(
- queryset=queryset,
+ queryset=table.get_queryset(),
columns=our_cols,
searching_columns=cls.searchable_columns)
)(request)
@@ -73,6 +97,8 @@
'sAjaxSource': self.source,
'bFilter': bool(self.searchable_columns)
})
+ if self.total_length is not None:
+ opts['iDeferLoading'] = self.total_length
aoColumnDefs = opts['aoColumnDefs'] = []
for col in self.columns:
aoColumnDefs.append({
@@ -80,6 +106,11 @@
'mDataProp': col.name,
'aTargets': [col.name],
})
- if col.column.width:
- aoColumnDefs[-1]['sWidth'] = col.column.width
return simplejson.dumps(opts)
+
+ datatable_opts = {}
+ searchable_columns = []
+
+ def get_queryset(self):
+ raise NotImplementedError
+
=== modified file 'lava_scheduler_app/views.py'
@@ -19,6 +19,10 @@
)
from django.template import RequestContext
from django.template import defaultfilters as filters
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+
+from django_tables2 import Attrs, Column
from lava_server.views import index as lava_index
from lava_server.bread_crumbs import (
@@ -37,7 +41,6 @@
TestJob,
)
from lava_scheduler_app.tables import (
- AjaxColumn,
AjaxTable,
)
@@ -51,7 +54,7 @@
return decorated
-class DateColumn(AjaxColumn):
+class DateColumn(Column):
def __init__(self, **kw):
self._format = kw.get('date_format', settings.DATETIME_FORMAT)
@@ -61,14 +64,20 @@
return filters.date(value, self._format)
-class IDLinkColumn(AjaxColumn):
+def pklink(record):
+ return mark_safe(
+ '<a href="%s">%s</a>' % (
+ record.get_absolute_url(),
+ escape(record.pk)))
+
+class IDLinkColumn(Column):
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)
+ return pklink(record)
def all_jobs_with_device_sort():
@@ -84,19 +93,24 @@
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)
+ return pklink(record.actual_device)
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>'
+ return pklink(record.requested_device)
+ else:
+ return mark_safe(
+ '<i>' + escape(record.requested_device_type.pk) + '</i>')
+
+ def render_description(self, value):
+ if value:
+ return value
+ else:
+ return ''
id = IDLinkColumn()
- status = AjaxColumn()
- device = AjaxColumn(sort_expr='device_sort')
- description = AjaxColumn(width="30%")
- submitter = AjaxColumn(accessor='submitter.username')
+ status = Column()
+ device = Column(accessor='device_sort')
+ description = Column(attrs=Attrs(width="30%"))
+ submitter = Column()
submit_time = DateColumn()
end_time = DateColumn()
@@ -107,29 +121,33 @@
class IndexJobTable(JobTable):
+ def get_queryset(self):
+ return all_jobs_with_device_sort().filter(
+ status__in=[TestJob.SUBMITTED, TestJob.RUNNING])
+
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]))
+ return IndexJobTable.json(request)
class DeviceTable(AjaxTable):
+ def get_queryset(self):
+ return Device.objects.select_related("device_type")
+
hostname = IDLinkColumn("hostname")
- device_type = AjaxColumn(accessor='device_type.pk')
- status = AjaxColumn()
- health_status = AjaxColumn()
+ device_type = Column()
+ status = Column()
+ health_status = Column()
searchable_columns=['hostname']
def index_devices_json(request):
- return DeviceTable.json(
- request, Device.objects.select_related("device_type"))
+ return DeviceTable.json(request)
@BreadCrumb("Scheduler", parent=lava_index)
@@ -147,20 +165,26 @@
class DeviceHealthTable(AjaxTable):
+ def get_queryset(self):
+ return Device.objects.select_related(
+ "hostname", "last_health_report_job")
+
def render_hostname(self, record):
- return '<a href="%s">%s</a>' % (record.get_device_health_url(), record.pk)
+ return pklink(record)
- def render_last_report_job(self, record):
+ def render_last_health_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)
+ return pklink(report)
- hostname = AjaxColumn("hostname")
- health_status = AjaxColumn()
- last_report_time = DateColumn(accessor="last_health_report_job.end_time")
- last_report_job = AjaxColumn()
+ hostname = Column("hostname")
+ health_status = Column()
+ last_report_time = DateColumn(
+ verbose_name="last report time",
+ accessor="last_health_report_job.end_time")
+ last_health_report_job = Column("last report job")
searchable_columns=['hostname']
datatable_opts = {
@@ -169,9 +193,7 @@
def lab_health_json(request):
- return DeviceHealthTable.json(
- request, Device.objects.select_related(
- "hostname", "last_health_report_job"))
+ return DeviceHealthTable.json(request)
@BreadCrumb("All Device Health", parent=index)
@@ -187,19 +209,22 @@
class HealthJobTable(JobTable):
+
+ def get_queryset(self):
+ device, = self.params
+ TestJob.objects.select_related(
+ "submitter",
+ ).filter(
+ actual_device=device,
+ health_check=True)
+
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))
+ return HealthJobTable.json(params=(device,))
@BreadCrumb("All Health Jobs on Device {pk}", parent=index, needs=['pk'])
@@ -211,7 +236,8 @@
{
'device': device,
'health_job_table': HealthJobTable(
- 'health_jobs', reverse(health_jobs_json, kwargs=dict(pk=pk))),
+ 'health_jobs', reverse(health_jobs_json, kwargs=dict(pk=pk)),
+ params=(device,)),
'show_maintenance': device.can_admin(request.user) and \
device.status in [Device.IDLE, Device.RUNNING],
'show_online': device.can_admin(request.user) and \
@@ -223,6 +249,9 @@
class AllJobsTable(JobTable):
+ def get_queryset(self):
+ return all_jobs_with_device_sort()
+
datatable_opts = JobTable.datatable_opts.copy()
datatable_opts.update({
@@ -231,8 +260,7 @@
def alljobs_json(request):
- return AllJobsTable.json(
- request, all_jobs_with_device_sort())
+ return AllJobsTable.json(request)
@BreadCrumb("All Jobs", parent=index)
@@ -420,17 +448,34 @@
class RecentJobsTable(JobTable):
+
+ def get_queryset(self):
+ device, = self.params
+ return device.recent_jobs()
+
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())
+ return RecentJobsTable.json(request, params=(device,))
class DeviceTransitionTable(AjaxTable):
+ def get_queryset(self):
+ device, = self.params
+ 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 qs
+
def render_created_on(self, record):
t = record
base = filters.date(t.created_on, "Y-m-d H:i")
@@ -448,10 +493,10 @@
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')
+ created_on = Column('when', attrs=Attrs(width="40%"))
+ transition = Column('transition', sortable=False)
+ created_by = Column('by')
+ message = Column('reason')
datatable_opts = {
'aaSorting': [[0, 'desc']],
@@ -460,16 +505,7 @@
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)
-
+ return DeviceTransitionTable.json(request, params=(device,))
@BreadCrumb("Device {pk}", parent=index, needs=['pk'])
@@ -488,9 +524,11 @@
'device': device,
'transition': transition,
'transition_table': DeviceTransitionTable(
- 'transitions', reverse(transition_json, kwargs=dict(pk=device.pk))),
+ 'transitions', reverse(transition_json, kwargs=dict(pk=device.pk)),
+ params=(device,)),
'recent_job_table': RecentJobsTable(
- 'jobs', reverse(recent_jobs_json, kwargs=dict(pk=device.pk))),
+ 'jobs', reverse(recent_jobs_json, kwargs=dict(pk=device.pk)),
+ params=(device,)),
'show_maintenance': device.can_admin(request.user) and \
device.status in [Device.IDLE, Device.RUNNING],
'show_online': device.can_admin(request.user) and \