=== modified file 'dashboard_app/templates/dashboard_app/bundle_list.html'
--- dashboard_app/templates/dashboard_app/bundle_list.html	2011-12-15 16:13:43 +0000
+++ dashboard_app/templates/dashboard_app/bundle_list.html	2012-03-12 21:00:15 +0000
@@ -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'
--- dashboard_app/templates/dashboard_app/bundle_stream_list.html	2011-07-12 02:34:12 +0000
+++ dashboard_app/templates/dashboard_app/bundle_stream_list.html	2012-03-12 03:58:38 +0000
@@ -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'
--- dashboard_app/templates/dashboard_app/test_run_detail.html	2011-09-27 20:58:16 +0000
+++ dashboard_app/templates/dashboard_app/test_run_detail.html	2012-03-12 05:02:49 +0000
@@ -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'
--- dashboard_app/templates/dashboard_app/test_run_list.html	2011-09-27 21:26:42 +0000
+++ dashboard_app/templates/dashboard_app/test_run_list.html	2012-03-16 03:31:30 +0000
@@ -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'
--- dashboard_app/urls.py	2011-10-06 12:03:00 +0000
+++ dashboard_app/urls.py	2012-03-16 03:31:30 +0000
@@ -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'
--- dashboard_app/views.py	2011-12-15 16:13:43 +0000
+++ dashboard_app/views.py	2012-03-20 21:19:58 +0000
@@ -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'
--- doc/changes.rst	2012-03-17 11:15:24 +0000
+++ doc/changes.rst	2012-03-20 21:26:58 +0000
@@ -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
 

