=== modified file 'dashboard_app/templates/dashboard_app/bundle_list.html'
@@ -1,4 +1,6 @@
{% extends "dashboard_app/_content_with_sidebar.html" %}
+
+{% load django_tables2 %}
{% load i18n %}
{% load humanize %}
@@ -32,14 +34,6 @@
</div>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
- oTable = $('#bundles').dataTable({
- bJQueryUI: true,
- sPaginationType: "full_numbers",
- aaSorting: [[4, "desc"]],
- iDisplayLength: 25,
- aLengthMenu: [[10, 25, 50, -1], [10, 25, 50, "All"]],
- sDom: 'lfr<"#master-toolbar">t<"F"ip>'
- });
// Try hard to make our radio boxes behave as links
$("#master-toolbar-splice span.view-as").buttonset();
$("#master-toolbar-splice span.view-as input").change(function(event) {
@@ -56,39 +50,10 @@
$("#master-toolbar").addClass("ui-widget-header ui-corner-tl ui-corner-tr").css(
"padding", "5pt").css("text-align", "center");
});
-</script>
-<table class="demo_jui display" id="bundles">
- <thead>
- <tr>
- <th>{% trans "Bundle Name" %}</th>
- <th>{% trans "Passes" %}</th>
- <th>{% trans "Fails " %}</th>
- <th>{% trans "Total Results" %}</th>
- <th>{% trans "Uploaded On" %}</th>
- <th>{% trans "Uploaded By" %}</th>
- <th>{% trans "Deserialized?" %}</th>
- </tr>
- </thead>
- <tbody>
- {% for bundle in bundle_list %}
- <tr>
- <td><a href="{{ bundle.get_absolute_url }}"><code>{{ bundle.content_filename }}</code></a></td>
- <td>{{ bundle.get_summary_results.pass }}</td>
- <td>{{ bundle.get_summary_results.fail }}</td>
- <td>{{ bundle.get_summary_results.total }}</td>
- <td>{{ bundle.uploaded_on|date:"Y-m-d H:i:s"}}</td>
- <td>
- {% if bundle.uploaded_by %}
- {{ bundle.uploaded_by }}
- {% else %}
- <em>{% trans "anonymous user" %}</em>
- {% endif %}
- </td>
- <td>{{ bundle.is_deserialized|yesno }}</td>
- </tr>
- {% endfor %}
- </tbody>
-</table>
+</script>
+
+{% render_table bundle_table %}
+
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/bundle_stream_list.html'
@@ -1,38 +1,14 @@
{% extends "dashboard_app/_content_with_sidebar.html" %}
+
+{% load django_tables2 %}
{% load i18n %}
{% load pagination_tags %}
{% block content %}
-<script type="text/javascript" charset="utf-8">
- $(document).ready(function() {
- oTable = $('#bundle_streams').dataTable({
- bJQueryUI: true,
- sPaginationType: "full_numbers",
- iDisplayLength: 25,
- });
- });
-</script>
-<table class="demo_jui display" id="bundle_streams">
- <thead>
- <tr>
- <th width="50%">Pathname</th>
- <th>Name</th>
- <th>Number of test runs</th>
- <th>Number of bundles</th>
- </tr>
- </thead>
- <tbody>
- {% for bundle_stream in bundle_stream_list %}
- <tr>
- <td style="vertical-align: top;"><a href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}"><code>{{ bundle_stream.pathname }}</code></a></td>
- <td>{{ bundle_stream.name|default:"<i>not set</i>" }}</td>
- <td style="vertical-align: top;"><a href="{% url dashboard_app.views.test_run_list bundle_stream.pathname %}">{{ bundle_stream.get_test_run_count }}</a></td>
- <td style="vertical-align: top;"><a href="{% url dashboard_app.views.bundle_list bundle_stream.pathname %}">{{ bundle_stream.bundles.count}}</a></td>
- </tr>
- {% endfor %}
- </tbody>
-</table>
+
+{% render_table bundle_stream_table %}
+
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/test_run_detail.html'
@@ -1,43 +1,14 @@
{% extends "dashboard_app/_content_with_sidebar.html" %}
+
+{% load django_tables2 %}
{% load i18n %}
{% load humanize %}
{% block content %}
-<script type="text/javascript" charset="utf-8">
- $(document).ready(function() {
- oTable = $('#test_results').dataTable({
- "bJQueryUI": true,
- "sPaginationType": "full_numbers",
- "aaSorting": [[0, "asc"]],
- });
- });
-</script>
-<table class="demo_jui display" id="test_results">
- <thead>
- <tr>
- <th>#</th>
- <th>{% trans "Test case" %}</th>
- <th>{% trans "Result" %}</th>
- <th>{% trans "Measurement" %}</th>
- </tr>
- </thead>
- <tbody>
- {% for test_result in test_run.get_results %}
- <tr>
- <td width="1%">{{ test_result.relative_index }}</td>
- <td>{{ test_result.test_case|default_if_none:"<em>Not specified</em>" }}</td>
- <td>
- <a href ="{{test_result.get_absolute_url}}">
- <img src="{{ STATIC_URL }}dashboard_app/images/icon-{{ test_result.result_code }}.png"
- alt="{{ test_result.get_result_display }}" width="16" height="16" border="0"/></a>
- <a href ="{{test_result.get_absolute_url}}">{{ test_result.get_result_display }}</a>
- </td>
- <td>{{ test_result.measurement|default_if_none:"Not specified" }} {{ test_result.units }}</td>
- </tr>
- {% endfor %}
- </tbody>
-</table>
+
+{% render_table test_table %}
+
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/test_run_list.html'
@@ -1,4 +1,6 @@
{% extends "dashboard_app/_content_with_sidebar.html" %}
+
+{% load django_tables2 %}
{% load i18n %}
{% load humanize %}
@@ -55,7 +57,7 @@
"padding", "5pt").css("text-align", "center");
});
</script>
-{% include "dashboard_app/_test_run_list_table.html" %}
+{% render_table test_run_table %}
{% endblock %}
=== modified file 'dashboard_app/urls.py'
@@ -46,16 +46,20 @@
'mapper': legacy_mapper,
'template_name': 'dashboard_app/api.html'}),
url(r'^streams/$', 'bundle_stream_list'),
+ url(r'^streams/json$', 'bundle_stream_list_json'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/$', 'bundle_list'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/json$', 'bundle_list_table_json'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/$', 'bundle_detail'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/json$', 'bundle_json'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/$', 'test_run_detail'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/json$', 'test_run_detail_test_json'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/attachments$', 'attachment_list'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/attachments/(?P<pk>[0-9]+)/$', 'attachment_detail'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/result/(?P<relative_index>[0-9]+)/$', 'test_result_detail'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/hardware-context/$', 'test_run_hardware_context'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)bundles/(?P<content_sha1>[0-9a-z]+)/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/software-context/$', 'test_run_software_context'),
url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)test-runs/$', 'test_run_list'),
+ url(r'^streams(?P<pathname>/[a-zA-Z0-9/._-]+)test-runs/json$', 'test_run_list_json'),
url(r'^permalink/test-run/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/$', 'redirect_to_test_run'),
url(r'^permalink/test-run/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/(?P<trailing>.*)$', 'redirect_to_test_run'),
url(r'^permalink/test-result/(?P<analyzer_assigned_uuid>[a-zA-Z0-9-]+)/(?P<relative_index>[0-9]+)/$', 'redirect_to_test_result'),
=== modified file 'dashboard_app/views.py'
@@ -25,14 +25,24 @@
from django.contrib.auth.decorators import login_required
from django.contrib.sites.models import Site
+from django.core.urlresolvers import reverse
from django.db.models.manager import Manager
from django.db.models.query import QuerySet
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect, get_object_or_404
from django.template import RequestContext, loader
-from django.views.generic.create_update import create_object
+from django.utils.safestring import mark_safe
from django.views.generic.list_detail import object_list, object_detail
+from django_tables2 import Attrs, Column, TemplateColumn
+
+from lava.utils.data_tables.tables import DataTablesTable
+from lava_server.views import index as lava_index
+from lava_server.bread_crumbs import (
+ BreadCrumb,
+ BreadCrumbTrail,
+)
+
from dashboard_app.models import (
Attachment,
Bundle,
@@ -46,11 +56,6 @@
TestRun,
TestingEffort,
)
-from lava_server.views import index as lava_index
-from lava_server.bread_crumbs import (
- BreadCrumb,
- BreadCrumbTrail,
-)
def _get_queryset(klass):
@@ -99,6 +104,37 @@
}, RequestContext(request))
+
+
+class BundleStreamTable(DataTablesTable):
+
+ pathname = TemplateColumn(
+ '<a href="{% url dashboard_app.views.bundle_list record.pathname %}">'
+ '<code>{{ record.pathname }}</code></a>')
+ name = TemplateColumn(
+ '{{ record.name|default:"<i>not set</i>" }}')
+ number_of_test_runs = TemplateColumn(
+ '<a href="{% url dashboard_app.views.test_run_list record.pathname %}">'
+ '{{ record.get_test_run_count }}')
+ number_of_bundles = TemplateColumn(
+ '<a href="{% url dashboard_app.views.bundle_list record.pathname %}">'
+ '{{ record.bundles.count}}</a>')
+
+ def get_queryset(self, user):
+ return BundleStream.objects.accessible_by_principal(user)
+
+ datatable_opts = {
+ 'iDisplayLength': 25,
+ 'sPaginationType': "full_numbers",
+ }
+
+ searchable_columns = ['pathname', 'name']
+
+
+def bundle_stream_list_json(request):
+ return BundleStreamTable.json(request, params=(request.user,))
+
+
@BreadCrumb("Bundle Streams", parent=index)
def bundle_stream_list(request):
"""
@@ -108,7 +144,9 @@
'dashboard_app/bundle_stream_list.html', {
'bread_crumb_trail': BreadCrumbTrail.leading_to(
bundle_stream_list),
- "bundle_stream_list": BundleStream.objects.accessible_by_principal(request.user).order_by('pathname'),
+ "bundle_stream_table": BundleStreamTable(
+ 'bundle-stream-table', reverse(bundle_stream_list_json),
+ params=(request.user,)),
'has_personal_streams': (
request.user.is_authenticated() and
BundleStream.objects.filter(user=request.user).count() > 0),
@@ -120,6 +158,52 @@
)
+class BundleTable(DataTablesTable):
+
+ content_filename = TemplateColumn(
+ '<a href="{{ record.get_absolute_url }}">'
+ '<code>{{ record.content_filename }}</code></a>',
+ verbose_name="bundle name")
+
+ passes = TemplateColumn('{{ record.get_summary_results.pass }}')
+ fails = TemplateColumn('{{ record.get_summary_results.pass }}')
+ total_results = TemplateColumn('{{ record.get_summary_results.total }}')
+
+ uploaded_on = TemplateColumn('{{ record.uploaded_on|date:"Y-m-d H:i:s"}}')
+ uploaded_by = TemplateColumn('''
+ {% load i18n %}
+ {% if record.uploaded_by %}
+ {{ record.uploaded_by }}
+ {% else %}
+ <em>{% trans "anonymous user" %}</em>
+ {% endif %}''')
+ deserializaled = TemplateColumn('{{ record.is_deserialized|yesno }}')
+
+ def get_queryset(self, bundle_stream):
+ return bundle_stream.bundles.select_related(
+ 'bundle_stream', 'deserialization_error')
+
+ datatable_opts = {
+ 'aaSorting': [[4, 'desc']],
+ 'sPaginationType': 'full_numbers',
+ 'iDisplayLength': 25,
+# 'aLengthMenu': [[10, 25, 50, -1], [10, 25, 50, "All"]],
+ 'sDom': 'lfr<"#master-toolbar">t<"F"ip>',
+ }
+
+ searchable_columns = ['content_filename']
+
+
+def bundle_list_table_json(request, pathname):
+ bundle_stream = get_restricted_object_or_404(
+ BundleStream,
+ lambda bundle_stream: bundle_stream,
+ request.user,
+ pathname=pathname
+ )
+ return BundleTable.json(request, params=(bundle_stream,))
+
+
@BreadCrumb(
"Bundles in {pathname}",
parent=bundle_stream_list,
@@ -134,18 +218,20 @@
request.user,
pathname=pathname
)
- return object_list(
- request,
- queryset=bundle_stream.bundles.select_related(
- 'bundle_stream', 'deserialization_error'),
- template_name="dashboard_app/bundle_list.html",
- template_object_name="bundle",
- extra_context={
+ return render_to_response(
+ "dashboard_app/bundle_list.html",
+ {
+ 'bundle_table': BundleTable(
+ 'bundle-table',
+ reverse(
+ bundle_list_table_json, kwargs=dict(pathname=pathname)),
+ params=(bundle_stream,)),
'bread_crumb_trail': BreadCrumbTrail.leading_to(
bundle_list,
pathname=pathname),
"bundle_stream": bundle_stream
- })
+ },
+ RequestContext(request))
@BreadCrumb(
@@ -229,28 +315,28 @@
RequestContext(request))
-@BreadCrumb(
- "Test runs in {pathname}",
- parent=bundle_stream_list,
- needs=['pathname'])
-def test_run_list(request, pathname):
- """
- List of test runs in a specified bundle stream.
- """
- bundle_stream = get_restricted_object_or_404(
- BundleStream,
- lambda bundle_stream: bundle_stream,
- request.user,
- pathname=pathname
- )
- return render_to_response(
- 'dashboard_app/test_run_list.html', {
- 'bread_crumb_trail': BreadCrumbTrail.leading_to(
- test_run_list,
- pathname=pathname),
- "test_run_list": TestRun.objects.filter(
+class TestRunTable(DataTablesTable):
+
+ record = TemplateColumn(
+ '<a href="{{ record.get_absolute_url }}">'
+ '<code>{{ record.test }} results<code/></a>',
+ accessor="test__test_id",
+ )
+
+ test = TemplateColumn(
+ '<a href="{{ record.test.get_absolute_url }}">{{ record.test }}</a>',
+ accessor="test__test_id",
+ )
+
+ uploaded_on = TemplateColumn(
+ '{{ record.bundle.uploaded_on|date:"Y-m-d H:i:s" }}')
+
+ analyzed_on = TemplateColumn(
+ '{{ record.analyzer_assigned_date|date:"Y-m-d H:i:s" }}')
+
+ def get_queryset(self, bundle_stream):
+ return TestRun.objects.filter(
bundle__bundle_stream=bundle_stream
- ).order_by( # clean any implicit ordering
).select_related(
"test",
"bundle",
@@ -264,12 +350,94 @@
"bundle__bundle_stream__pathname", # Needed by TestRun.get_absolute_url
"test__name", # needed by Test.__unicode__
"test__test_id", # needed by Test.__unicode__
- ),
+ )
+
+ datatable_opts = {
+ "sPaginationType": "full_numbers",
+ "aaSorting": [[1, "desc"]],
+ "iDisplayLength": 25,
+ "sDom": 'lfr<"#master-toolbar">t<"F"ip>'
+ }
+
+ searchable_columns = ['test__test_id']
+
+
+def test_run_list_json(request, pathname):
+ bundle_stream = get_restricted_object_or_404(
+ BundleStream,
+ lambda bundle_stream: bundle_stream,
+ request.user,
+ pathname=pathname
+ )
+ return TestRunTable.json(request, params=(bundle_stream,))
+
+
+@BreadCrumb(
+ "Test runs in {pathname}",
+ parent=bundle_stream_list,
+ needs=['pathname'])
+def test_run_list(request, pathname):
+ """
+ List of test runs in a specified bundle stream.
+ """
+ bundle_stream = get_restricted_object_or_404(
+ BundleStream,
+ lambda bundle_stream: bundle_stream,
+ request.user,
+ pathname=pathname
+ )
+ return render_to_response(
+ 'dashboard_app/test_run_list.html', {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ test_run_list,
+ pathname=pathname),
+ "test_run_table": TestRunTable(
+ 'test-runs',
+ reverse(test_run_list_json, kwargs=dict(pathname=pathname)),
+ params=(bundle_stream,)),
"bundle_stream": bundle_stream,
}, RequestContext(request)
)
+class TestTable(DataTablesTable):
+
+ relative_index = Column(
+ verbose_name="#", attrs=Attrs(th=dict(style="width: 1%")),
+ default=mark_safe("<em>Not specified</em>"))
+
+ test_case = Column()
+
+ result = TemplateColumn('''
+ <a href="{{record.get_absolute_url}}">
+ <img src="{{ STATIC_URL }}dashboard_app/images/icon-{{ record.result_code }}.png"
+ alt="{{ record.get_result_display }}" width="16" height="16" border="0"/></a>
+ <a href ="{{record.get_absolute_url}}">{{ record.get_result_display }}</a>
+ ''')
+
+ units = TemplateColumn(
+ '{{ record.measurement|default_if_none:"Not specified" }} {{ record.units }}',
+ verbose_name="measurement")
+
+ def get_queryset(self, test_run):
+ return test_run.get_results()
+
+ datatable_opts = {
+ 'sPaginationType': "full_numbers",
+ }
+
+ searchable_columns = ['test_case__test_case_id']
+
+
+def test_run_detail_test_json(request, pathname, content_sha1, analyzer_assigned_uuid):
+ test_run = get_restricted_object_or_404(
+ TestRun, lambda test_run: test_run.bundle.bundle_stream,
+ request.user,
+ analyzer_assigned_uuid=analyzer_assigned_uuid
+ )
+ return TestTable.json(request, params=(test_run,))
+
+
@BreadCrumb(
"Run {analyzer_assigned_uuid}",
parent=bundle_detail,
@@ -288,7 +456,15 @@
pathname=pathname,
content_sha1=content_sha1,
analyzer_assigned_uuid=analyzer_assigned_uuid),
- "test_run": test_run
+ "test_run": test_run,
+ "test_table": TestTable(
+ 'test-table',
+ reverse(test_run_detail_test_json, kwargs=dict(
+ pathname=pathname,
+ content_sha1=content_sha1,
+ analyzer_assigned_uuid=analyzer_assigned_uuid)),
+ params=(test_run,))
+
}, RequestContext(request))
=== modified file 'doc/changes.rst'
@@ -6,6 +6,7 @@
Version 0.14
============
+* Convert some tables to use AJAX pagination.
* Add an admin function to support deleting an entire bundle, including
referenced test runs and results