diff mbox

[Branch,~linaro-validation/lava-dashboard/trunk] Rev 341: add the ability to group and order filter matches by a build number

Message ID 20120909230713.6896.92868.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

Michael-Doyle Hudson Sept. 9, 2012, 11:07 p.m. UTC
Merge authors:
  Michael Hudson-Doyle (mwhudson)
Related merge proposals:
  https://code.launchpad.net/~mwhudson/lava-dashboard/trf-order-by-group-by-attribute/+merge/122996
  proposed by: Michael Hudson-Doyle (mwhudson)
------------------------------------------------------------
revno: 341 [merge]
committer: Michael Hudson-Doyle <michael.hudson@linaro.org>
branch nick: trunk
timestamp: Mon 2012-09-10 11:05:58 +1200
message:
  add the ability to group and order filter matches by a build number
added:
  dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py
  dashboard_app/migrations/0021_add_cast_integer.py
modified:
  dashboard_app/models.py
  dashboard_app/templates/dashboard_app/filter_form.html
  dashboard_app/templates/dashboard_app/filter_summary.html
  dashboard_app/views.py


--
lp:lava-dashboard
https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk

You are subscribed to branch lp:lava-dashboard.
To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk/+edit-subscription
diff mbox

Patch

=== added file 'dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py'
--- dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py	1970-01-01 00:00:00 +0000
+++ dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py	2012-09-04 00:37:05 +0000
@@ -0,0 +1,265 @@ 
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding field 'TestRunFilter.build_number_attribute'
+        db.add_column('dashboard_app_testrunfilter', 'build_number_attribute',
+                      self.gf('django.db.models.fields.CharField')(max_length=1024, null=True, blank=True),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'TestRunFilter.build_number_attribute'
+        db.delete_column('dashboard_app_testrunfilter', 'build_number_attribute')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'dashboard_app.attachment': {
+            'Meta': {'object_name': 'Attachment'},
+            'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+            'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'})
+        },
+        'dashboard_app.bundle': {
+            'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
+            '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
+            '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
+            'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
+            'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
+            'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
+        },
+        'dashboard_app.bundledeserializationerror': {
+            'Meta': {'object_name': 'BundleDeserializationError'},
+            'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}),
+            'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'})
+        },
+        'dashboard_app.bundlestream': {
+            'Meta': {'object_name': 'BundleStream'},
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.hardwaredevice': {
+            'Meta': {'object_name': 'HardwareDevice'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'dashboard_app.image': {
+            'Meta': {'object_name': 'Image'},
+            'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}),
+            'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.imageattribute': {
+            'Meta': {'object_name': 'ImageAttribute'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'required_attributes'", 'to': "orm['dashboard_app.Image']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'dashboard_app.imageset': {
+            'Meta': {'object_name': 'ImageSet'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
+        },
+        'dashboard_app.launchpadbug': {
+            'Meta': {'object_name': 'LaunchpadBug'},
+            'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"})
+        },
+        'dashboard_app.namedattribute': {
+            'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'value': ('django.db.models.fields.TextField', [], {})
+        },
+        'dashboard_app.softwarepackage': {
+            'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'dashboard_app.softwarepackagescratch': {
+            'Meta': {'object_name': 'SoftwarePackageScratch'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'dashboard_app.softwaresource': {
+            'Meta': {'object_name': 'SoftwareSource'},
+            'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
+            'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'})
+        },
+        'dashboard_app.tag': {
+            'Meta': {'object_name': 'Tag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'})
+        },
+        'dashboard_app.test': {
+            'Meta': {'object_name': 'Test'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        'dashboard_app.testcase': {
+            'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}),
+            'test_case_id': ('django.db.models.fields.TextField', [], {}),
+            'units': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'dashboard_app.testingeffort': {
+            'Meta': {'object_name': 'TestingEffort'},
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'testing_efforts'", 'to': "orm['lava_projects.Project']"}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'testing_efforts'", 'symmetrical': 'False', 'to': "orm['dashboard_app.Tag']"})
+        },
+        'dashboard_app.testresult': {
+            'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'},
+            '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}),
+            'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}),
+            'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.testrun': {
+            'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'},
+            'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
+            'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}),
+            'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}),
+            'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}),
+            'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}),
+            'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        'dashboard_app.testrundenormalization': {
+            'Meta': {'object_name': 'TestRunDenormalization'},
+            'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"})
+        },
+        'dashboard_app.testrunfilter': {
+            'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'},
+            'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Test']", 'null': 'True', 'blank': 'True'}),
+            'test_case': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestCase']", 'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.testrunfilterattribute': {
+            'Meta': {'object_name': 'TestRunFilterAttribute'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'dashboard_app.testrunfiltersubscription': {
+            'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'lava_projects.project': {
+            'Meta': {'object_name': 'Project'},
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}),
+            'is_aggregate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}),
+            'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['dashboard_app']
\ No newline at end of file

=== added file 'dashboard_app/migrations/0021_add_cast_integer.py'
--- dashboard_app/migrations/0021_add_cast_integer.py	1970-01-01 00:00:00 +0000
+++ dashboard_app/migrations/0021_add_cast_integer.py	2012-09-06 03:12:26 +0000
@@ -0,0 +1,280 @@ 
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+import django.db.utils
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        db.start_transaction()
+        try:
+            db.execute("CREATE LANGUAGE plpgsql")
+        except django.db.utils.DatabaseError:
+            db.rollback_transaction()
+            db.start_transaction()
+        db.execute("""
+CREATE FUNCTION convert_to_integer(v_input text)
+RETURNS INTEGER AS $a$
+DECLARE v_int_value INTEGER DEFAULT NULL;
+BEGIN
+    BEGIN
+        v_int_value := v_input::INTEGER;
+    EXCEPTION WHEN OTHERS THEN
+        RETURN NULL;
+    END;
+RETURN v_int_value;
+END;
+$a$ LANGUAGE plpgsql;
+        """)
+        db.commit_transaction()
+
+    def backwards(self, orm):
+        db.execute("""DROP FUNCTION convert_to_integer (v_input text)""")
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'dashboard_app.attachment': {
+            'Meta': {'object_name': 'Attachment'},
+            'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+            'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'})
+        },
+        'dashboard_app.bundle': {
+            'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
+            '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
+            '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
+            'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
+            'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
+            'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
+        },
+        'dashboard_app.bundledeserializationerror': {
+            'Meta': {'object_name': 'BundleDeserializationError'},
+            'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}),
+            'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'})
+        },
+        'dashboard_app.bundlestream': {
+            'Meta': {'object_name': 'BundleStream'},
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.hardwaredevice': {
+            'Meta': {'object_name': 'HardwareDevice'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'dashboard_app.image': {
+            'Meta': {'object_name': 'Image'},
+            'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}),
+            'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.imageattribute': {
+            'Meta': {'object_name': 'ImageAttribute'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'required_attributes'", 'to': "orm['dashboard_app.Image']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'dashboard_app.imageset': {
+            'Meta': {'object_name': 'ImageSet'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
+        },
+        'dashboard_app.launchpadbug': {
+            'Meta': {'object_name': 'LaunchpadBug'},
+            'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"})
+        },
+        'dashboard_app.namedattribute': {
+            'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'value': ('django.db.models.fields.TextField', [], {})
+        },
+        'dashboard_app.softwarepackage': {
+            'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'dashboard_app.softwarepackagescratch': {
+            'Meta': {'object_name': 'SoftwarePackageScratch'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'dashboard_app.softwaresource': {
+            'Meta': {'object_name': 'SoftwareSource'},
+            'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
+            'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'})
+        },
+        'dashboard_app.tag': {
+            'Meta': {'object_name': 'Tag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'})
+        },
+        'dashboard_app.test': {
+            'Meta': {'object_name': 'Test'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        'dashboard_app.testcase': {
+            'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}),
+            'test_case_id': ('django.db.models.fields.TextField', [], {}),
+            'units': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'dashboard_app.testingeffort': {
+            'Meta': {'object_name': 'TestingEffort'},
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'testing_efforts'", 'to': "orm['lava_projects.Project']"}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'testing_efforts'", 'symmetrical': 'False', 'to': "orm['dashboard_app.Tag']"})
+        },
+        'dashboard_app.testresult': {
+            'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'},
+            '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}),
+            'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}),
+            'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.testrun': {
+            'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'},
+            'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
+            'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}),
+            'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}),
+            'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}),
+            'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}),
+            'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        'dashboard_app.testrundenormalization': {
+            'Meta': {'object_name': 'TestRunDenormalization'},
+            'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"})
+        },
+        'dashboard_app.testrunfilter': {
+            'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'},
+            'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Test']", 'null': 'True', 'blank': 'True'}),
+            'test_case': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestCase']", 'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.testrunfilterattribute': {
+            'Meta': {'object_name': 'TestRunFilterAttribute'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'dashboard_app.testrunfiltersubscription': {
+            'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'lava_projects.project': {
+            'Meta': {'object_name': 'Project'},
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}),
+            'is_aggregate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}),
+            'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['dashboard_app']

=== modified file 'dashboard_app/models.py'
--- dashboard_app/models.py	2012-08-31 01:24:49 +0000
+++ dashboard_app/models.py	2012-09-06 21:24:39 +0000
@@ -43,6 +43,7 @@ 
 from django.db import models
 from django.db.models.fields import FieldDoesNotExist
 from django.db.models.signals import post_delete
+from django.db.models.sql.aggregates import Aggregate as SQLAggregate
 from django.dispatch import receiver
 from django.template import Template, Context
 from django.template.defaultfilters import filesizeformat
@@ -1505,7 +1506,7 @@ 
             test_runs__test__test_id='lava',
             test_runs__attributes__name=self.build_number_attribute).extra(
             select={
-                'build_number': 'cast("dashboard_app_namedattribute"."value" as int)'
+                'build_number': 'convert_to_integer("dashboard_app_namedattribute"."value")',
                 }).extra(
             order_by=['-build_number'],
             )[:count]
@@ -1571,32 +1572,46 @@ 
     TestRunFilter.get_test_runs.
     """
 
-    bundle = None
-    specific_results = None
-    result_count = None
-    pass_count = None
-    test_run = None
     filter = None
+    tag = None # either a date (bundle__uploaded_on) or a build number
+    test_runs = None
+    specific_results = None # Will stay none unless filter specifies a test case
+    pass_count = None # Only filled out for filters that dont specify a test
+    result_code = None # Ditto
+
+    def _format_test_result(self, test_case, result):
+        if test_case.units:
+            if self.filter.test_case.units:
+                return '%s%s' % (result.measurement, result.units)
+            else:
+                return result.RESULT_MAP[result.result]
+
+    def _format_test_run(self, test, tr):
+        return "%s %s pass / %s total" % (
+            test.test_id,
+            tr.denormalization.count_pass,
+            tr.denormalization.count_all())
+
+    def _format_many_test_runs(self):
+        return "%s pass / %s total" % (self.pass_count, self.result_count)
 
     def format_for_mail(self):
         r = [' ~%s/%s ' % (self.filter.owner.username, self.filter.name)]
         if self.filter.test_case:
-            r.extend([
+            r.append("%s:%s" % (
                 self.filter.test.test_id,
-                ':',
                 self.filter.test_case.test_case_id,
-                ])
-            for result in self.specific_results:
-                if self.filter.test_case.units:
-                    result_desc = '%s%s' % (result.measurement, result.units)
-                else:
-                    result_desc = result.RESULT_MAP[result.result]
-                r.extend([' ', result_desc])
+                ))
+            r.append(' ' + ', '.join(
+                self._format_test_result(self.filter.test_case, r)
+                for r in self.specific_results))
         elif self.filter.test:
-            r.append('%s %s pass/%s total' % (
-                self.filter.test.test_id, self.pass_count, self.result_count))
+            r.append(self.filter.test.test_id)
+            r.append(' ' + ', '.join(
+                self._format_test_run(self.filter.test, tr)
+                for tr in self.test_runs))
         else:
-            r.append('%s pass/%s total' % (self.pass_count, self.result_count))
+            r.append(self._format_many_test_runs())
         r.append('\n')
         return ''.join(r)
 
@@ -1605,22 +1620,81 @@ 
     """Wrap a QuerySet and construct FilterMatchs from what the wrapped query
     set returns.
 
-    Just enough of the QuerySet API to work with DataTable."""
+    Just enough of the QuerySet API to work with DataTable (i.e. ordering and
+    slicing)."""
 
     model = TestRun
 
-    def __init__(self, queryset, filter):
+    def __init__(self, queryset, filter_data):
         self.queryset = queryset
-        self.filter = filter
+        self.filter_data = filter_data
+        if filter_data['build_number_attribute']:
+            self.key = 'build_number'
+            self.key_name = 'Build'
+        else:
+            self.key = 'bundle__uploaded_on'
+            self.key_name = 'Uploaded On'
+        if filter_data['test_case']:
+            self.has_specific_results = True
+        else:
+            self.has_specific_results = False
 
     def _makeMatches(self, data):
-        raise NotImplementedError(self._makeMatches)
+        test_run_ids = set()
+        for datum in data:
+            test_run_ids.update(datum['id__arrayagg'])
+        r = []
+        trs = TestRun.objects.filter(id__in=test_run_ids).select_related(
+            'denormalization', 'bundle', 'bundle__bundle_stream')
+        trs_by_id = {}
+        for tr in trs:
+            trs_by_id[tr.id] = tr
+        if self.has_specific_results:
+            result_ids_by_tr_id = {}
+            results_by_tr_id = {}
+            values = TestRun.objects.filter(
+                id__in=test_run_ids,
+                test_results__test_case=self.filter_data['test_case']).values_list(
+                'id', 'test_results')
+            result_ids = set()
+            for v in values:
+                result_ids_by_tr_id.setdefault(v[0], []).append(v[1])
+                result_ids.add(v[1])
+
+            results_by_id = {}
+            for result in TestResult.objects.filter(
+                id__in=list(result_ids)).select_related(
+                'test', 'test_case', 'test_run__bundle__bundle_stream'):
+                results_by_id[result.id] = result
+
+            for tr_id, result_ids in result_ids_by_tr_id.items():
+                rs = results_by_tr_id[tr_id] = []
+                for result_id in result_ids:
+                    rs.append(results_by_id[result_id])
+        for datum in data:
+            trs = []
+            for id in datum['id__arrayagg']:
+                trs.append(trs_by_id[id])
+            match = FilterMatch()
+            match.test_runs = trs
+            match.filter_data = self.filter_data
+            match.tag = datum[self.key]
+            if self.has_specific_results:
+                match.specific_results = []
+                for id in datum['id__arrayagg']:
+                    match.specific_results.extend(results_by_tr_id[id])
+            else:
+                match.pass_count = sum(tr.denormalization.count_pass for tr in trs)
+                match.result_count = sum(tr.denormalization.count_all() for tr in trs)
+            r.append(match)
+        return iter(r)
 
     def _wrap(self, queryset, **kw):
-        return self.__class__(queryset, self.filter, **kw)
+        return self.__class__(queryset, self.filter_data, **kw)
 
     def order_by(self, *args):
-        return self._wrap(self.queryset.order_by(*args))
+        # the generic tables code calls this even when it shouldn't...
+        return self
 
     def count(self):
         return self.queryset.count()
@@ -1633,102 +1707,6 @@ 
         return self._makeMatches(data)
 
 
-class SpecificTestCaseMatchMakingQuerySet(MatchMakingQuerySet):
-
-    def _makeMatches(self, runs):
-        results_by_run_id = {}
-        for run in runs:
-            results_by_run_id[run.id] = []
-        results = TestResult.objects.filter(
-            test_run_id__in=results_by_run_id.keys(),
-            test_case_id=self.filter.test_case.id)
-        for result in results:
-            results_by_run_id[result.test_run_id].append(result)
-        matches = []
-        for run in runs:
-            match = FilterMatch()
-            specific_results = results_by_run_id[result.test_run_id]
-            match.specific_results = specific_results
-            match.result_count = len(specific_results)
-            match.pass_count = len([r for r in specific_results if r.result == r.RESULT_PASS])
-            match.test_run = run
-            match.bundle = run.bundle
-            match.filter = self.filter
-            matches.append(match)
-        return iter(matches)
-
-
-
-class SpecificTestMatchMakingQuerySet(MatchMakingQuerySet):
-    def _makeMatches(self, runs):
-        matches = []
-        for run in runs:
-            match = FilterMatch()
-            match.specific_results = None
-            match.result_count = run.denormalization.count_all()
-            match.pass_count = run.denormalization.count_pass
-            match.test_run = run
-            match.bundle = run.bundle
-            match.filter = self.filter
-            matches.append(match)
-        return iter(matches)
-
-
-class BundleMatchMakingQuerySet(MatchMakingQuerySet):
-
-    model = Bundle
-
-    def __init__(self, queryset, filter, mis_ordered=False):
-        super(BundleMatchMakingQuerySet, self).__init__(queryset, filter)
-        self.mis_ordered = mis_ordered
-
-    def _makeMatches(self, bundles):
-        assert not self.mis_ordered, """
-           attempt to materialize BundleMatchMakingQuerySet when ordered on
-           non-bundle field"""
-        matches = []
-        counted_bundles = Bundle.objects.filter(
-            id__in=[b.id for b in bundles]).annotate(
-            pass_count=models.Sum('test_runs__denormalization__count_pass'),
-            unknown_count=models.Sum('test_runs__denormalization__count_unknown'),
-            skip_count=models.Sum('test_runs__denormalization__count_skip'),
-            fail_count=models.Sum('test_runs__denormalization__count_fail'))
-        bundles_by_id = {}
-        for bundle in counted_bundles:
-            bundles_by_id[bundle.id] = bundle
-        for bundle in bundles:
-            match = FilterMatch()
-            match.specific_results = None
-            cb = bundles_by_id[bundle.id]
-            match.result_count = cb.unknown_count + cb.skip_count + cb.pass_count + cb.fail_count
-            match.pass_count = cb.pass_count
-            match.test_run = None
-            match.bundle = bundle
-            match.filter = self.filter
-            matches.append(match)
-        return iter(matches)
-
-    def _wrap(self, queryset, **kw):
-        if 'mis_ordered' not in kw:
-            kw['mis_ordered'] = self.mis_ordered
-        return self.__class__(queryset, self.filter, **kw)
-
-    def order_by(self, field):
-        if field.startswith('bundle__') or field.startswith('-bundle__'):
-            if field.startswith('-'):
-                prefix = '-'
-                field = field[1:]
-            else:
-                prefix = ''
-            field = field[len('bundle__'):]
-            r = super(BundleMatchMakingQuerySet, self).order_by(
-                prefix+field)
-            r.mis_ordered = False
-            return r
-        else:
-            return self._wrap(self.queryset, mis_ordered=True)
-
-
 class TestRunFilterAttribute(models.Model):
 
     name = models.CharField(max_length=1024)
@@ -1740,6 +1718,22 @@ 
         return '%s = %s' % (self.name, self.value)
 
 
+class SQLArrayAgg(SQLAggregate):
+    sql_function = 'array_agg'
+
+
+class ArrayAgg(models.Aggregate):
+    name = 'ArrayAgg'
+    def add_to_query(self, query, alias, col, source, is_summary):
+        aggregate = SQLArrayAgg(
+            col, source=source, is_summary=is_summary, **self.extra)
+        # For way more detail than you want about what this next line is for,
+        # see
+        # http://voices.canonical.com/michael.hudson/2012/09/02/using-postgres-array_agg-from-django/
+        aggregate.field = models.DecimalField() # vomit
+        query.aggregates[alias] = aggregate
+
+
 class TestRunFilter(models.Model):
 
     owner = models.ForeignKey(User)
@@ -1765,6 +1759,10 @@ 
     public = models.BooleanField(
         default=False, help_text="Whether other users can see this filter.")
 
+    build_number_attribute = models.CharField(
+        max_length=1024, blank=True, null=True,
+        help_text="For some filters, there is a natural <b>build number</b>.  If you specify the name of the attribute that contains the build number here, the results of the filter will be grouped and ordered by this build number.")
+
     @property
     def summary_data(self):
         return {
@@ -1772,6 +1770,7 @@ 
             'attributes': self.attributes.all().values_list('name', 'value'),
             'test': self.test,
             'test_case': self.test_case,
+            'build_number_attribute': self.build_number_attribute,
             }
 
     def __unicode__(self):
@@ -1803,36 +1802,50 @@ 
     def get_test_runs_impl(self, user, bundle_streams, attributes):
         accessible_bundle_streams = BundleStream.objects.accessible_by_principal(
             user)
-        testruns = TestRun.objects.filter(
-            models.Q(bundle__bundle_stream__in=accessible_bundle_streams),
-            models.Q(bundle__bundle_stream__in=bundle_streams),
-            )
+        bs_ids = [bs.id for bs in set(accessible_bundle_streams) & set(bundle_streams)]
+        conditions = [models.Q(bundle__bundle_stream__id__in=bs_ids)]
+
+        content_type_id = ContentType.objects.get_for_model(TestRun).id
 
         for (name, value) in attributes:
-            testruns = TestRun.objects.filter(
-                id__in=testruns.values_list('id'),
-                attributes__name=name, attributes__value=value)
+            # We punch through the generic relation abstraction here for 100x
+            # better performance.
+            conditions.append(
+                models.Q(id__in=NamedAttribute.objects.filter(
+                    name=name, value=value, content_type_id=content_type_id
+                    ).values('object_id')))
 
         if self.test_case:
-            testruns = TestRun.objects.filter(
-                id__in=testruns.values_list('id'),
+            conditions.append(models.Q(
                 test_results__test_case=self.test_case,
-                test=self.test_case.test)
-            wrapper_cls = SpecificTestCaseMatchMakingQuerySet
+                test=self.test_case.test))
         elif self.test:
-            testruns = TestRun.objects.filter(
-                id__in=testruns.values_list('id'),
-                test=self.test)
-            wrapper_cls = SpecificTestMatchMakingQuerySet
+            conditions.append(models.Q(test=self.test))
+
+        testruns = TestRun.objects.filter(*conditions)
+
+        if self.build_number_attribute:
+            testruns = testruns.filter(
+                attributes__name=self.build_number_attribute).extra(
+                select={
+                    'build_number': 'convert_to_integer("dashboard_app_namedattribute"."value")',
+                    },
+                where=['convert_to_integer("dashboard_app_namedattribute"."value") IS NOT NULL']).extra(
+                order_by=['-build_number'],
+                ).values('build_number').annotate(ArrayAgg('id'))
         else:
-            # if the filter doesn't specify a test, we still only return one
-            # test run per bundle.  the display code knows to do different
-            # things in this case.
-            testruns = Bundle.objects.filter(
-                test_runs__in=testruns)
-            wrapper_cls = BundleMatchMakingQuerySet
-
-        return wrapper_cls(testruns, self)
+            testruns = testruns.order_by('-bundle__uploaded_on').values(
+                'bundle__uploaded_on').annotate(ArrayAgg('id'))
+
+        filter_data = {
+            'bundle_streams': bundle_streams,
+            'attributes': attributes,
+            'test': self.test,
+            'test_case': self.test_case,
+            'build_number_attribute': self.build_number_attribute,
+            }
+
+        return MatchMakingQuerySet(testruns, filter_data)
 
     # given bundle:
     # select from filter
@@ -1864,42 +1877,33 @@ 
                                           select django_content_type.id from django_content_type
                                           where app_label = 'dashboard_app' and model='testrun')
                                  and object_id = dashboard_app_testrun.id)))
-            from dashboard_app_testrun where dashboard_app_testrun.bundle_id = %s) = 0 """ % bundle.id],
+            from dashboard_app_testrun where dashboard_app_testrun.bundle_id = %s) = 0""" % bundle.id],
             )
         filters = list(filters)
         matches = []
+        bundle_with_counts = Bundle.objects.annotate(
+            pass_count=models.Sum('test_runs__denormalization__count_pass'),
+            unknown_count=models.Sum('test_runs__denormalization__count_unknown'),
+            skip_count=models.Sum('test_runs__denormalization__count_skip'),
+            fail_count=models.Sum('test_runs__denormalization__count_fail')).get(
+            id=bundle.id)
         for filter in filters:
             if filter.test:
-                for test_run in bundle.test_runs.filter(test=filter.test):
-                    match = FilterMatch()
-                    match.filter = filter
-                    match.test_run = test_run
-                    if filter.test_case:
-                        match.specific_results = list(
-                            test_run.test_results.filter(test_case=filter.test_case))
-                        match.result_count = len(match.specific_results)
-                        match.pass_count = len(
-                            [r for r in match.specific_results if r.result == r.RESULT_PASS])
-                    else:
-                        match.specific_results = None
-                        match.result_count = test_run.denormalization.count_all()
-                        match.pass_count = test_run.denormalization.count_pass
-                    matches.append(match)
+                match = FilterMatch()
+                match.test_runs = list(bundle.test_runs.filter(test=filter.test))
+                match.filter = filter
+                if filter.test_case:
+                    match.specific_results = list(
+                        TestResult.objects.filter(test_case=filter.test_case, test_run__bundle=bundle))
+                matches.append(match)
             else:
                 match = FilterMatch()
                 match.filter = filter
-                match.test_run = None
-                bundle_with_counts = Bundle.objects.annotate(
-                    pass_count=models.Sum('test_runs__denormalization__count_pass'),
-                    unknown_count=models.Sum('test_runs__denormalization__count_unknown'),
-                    skip_count=models.Sum('test_runs__denormalization__count_skip'),
-                    fail_count=models.Sum('test_runs__denormalization__count_fail')).get(
-                    id=bundle.id)
-                match.specific_results = None
+                match.test_runs = list(bundle.test_runs.all())
                 b = bundle_with_counts
                 match.result_count = b.unknown_count + b.skip_count + b.pass_count + b.fail_count
                 match.pass_count = bundle_with_counts.pass_count
-            matches.append(match)
+                matches.append(match)
         return matches
 
     def get_test_runs(self, user):

=== modified file 'dashboard_app/templates/dashboard_app/filter_form.html'
--- dashboard_app/templates/dashboard_app/filter_form.html	2012-08-16 00:14:22 +0000
+++ dashboard_app/templates/dashboard_app/filter_form.html	2012-09-04 02:13:52 +0000
@@ -23,6 +23,14 @@ 
         <div style="clear:left">{{ form.bundle_streams.help_text|safe }}</div>
       </dd>
       <dt>
+        Build Number:
+      </dt>
+      <dd>
+        {{ form.build_number_attribute.errors }}
+        {{ form.build_number_attribute.label_tag }}: {{ form.build_number_attribute }}
+        <br /><span class="helptext">{{ form.build_number_attribute.help_text|safe }}</span>
+      </dd>
+      <dt>
         Attributes:
       </dt>
       <dd>

=== modified file 'dashboard_app/templates/dashboard_app/filter_summary.html'
--- dashboard_app/templates/dashboard_app/filter_summary.html	2012-08-17 03:51:42 +0000
+++ dashboard_app/templates/dashboard_app/filter_summary.html	2012-09-05 02:59:43 +0000
@@ -21,6 +21,16 @@ 
     </td>
   </tr>
 {% endif %}
+{% if summary_data.build_number_attribute %}
+  <tr>
+    <th>
+      Build Number Attribute
+    </th>
+    <td>
+      {{ summary_data.build_number_attribute }}
+    </td>
+  </tr>
+{% endif %}
   <tr>
     <th>
       Test case

=== modified file 'dashboard_app/views.py'
--- dashboard_app/views.py	2012-09-06 22:01:28 +0000
+++ dashboard_app/views.py	2012-09-09 23:05:58 +0000
@@ -20,6 +20,7 @@ 
 Views for the Dashboard application
 """
 
+import operator
 import re
 import json
 
@@ -457,6 +458,12 @@ 
     {% endfor %}
     ''')
 
+    build_number_attribute = Column()
+    def render_build_number_attribute(self, value):
+        if not value:
+            return ''
+        return value
+
     attributes = TemplateColumn('''
     {% for a in record.attributes.all %}
     {{ a }}  <br />
@@ -543,53 +550,85 @@ 
 
 class FilterTable(DataTablesTable):
     def __init__(self, *args, **kwargs):
-        filter = kwargs['params'][1]
-        data = filter.summary_data
         super(FilterTable, self).__init__(*args, **kwargs)
-        if len(data['bundle_streams']) == 1:
-            del self.base_columns['bundle_stream']
-        if data['test_case']:
-            del self.base_columns['bundle']
-            del self.base_columns['passes']
-            del self.base_columns['total']
-            self.base_columns['specific_results'].verbose_name = mark_safe(
-                data['test_case'].test_case_id)
-        elif data['test']:
-            del self.base_columns['bundle']
-            del self.base_columns['specific_results']
+        match_maker = self.data.queryset
+        self.base_columns['tag'].verbose_name = match_maker.key_name
+        bundle_stream_col = self.base_columns.pop('bundle_stream')
+        bundle_col = self.base_columns.pop('bundle')
+        tag_col = self.base_columns.pop('tag')
+        test_run_col = self.base_columns.pop('test_run')
+        specific_results_col = self.base_columns.pop('specific_results')
+        if match_maker.filter_data['test_case']:
+            del self.base_columns['passes']
+            del self.base_columns['total']
+            col_name = '%s:%s' % (
+                match_maker.filter_data['test'].test_id,
+                match_maker.filter_data['test_case'].test_case_id
+                )
+            specific_results_col.verbose_name = mark_safe(col_name)
+            self.base_columns.insert(0, 'specific_results', specific_results_col)
+        elif match_maker.filter_data['test']:
+            del self.base_columns['passes']
+            del self.base_columns['total']
+            test_run_col.verbose_name = mark_safe(match_maker.filter_data['test'].test_id)
+            self.base_columns.insert(0, 'test_run', test_run_col)
         else:
-            del self.base_columns['test_run']
-            self.base_columns['passes']
-            self.base_columns['total']
-            del self.base_columns['specific_results']
-        uploaded_col_index = self.base_columns.keys().index('uploaded_on')
-        self.datatable_opts = self.datatable_opts.copy()
-        self.datatable_opts['aaSorting'] = [[uploaded_col_index, 'desc']]
-        self._compute_queryset(kwargs['params'])
-
-    bundle_stream = Column(accessor='bundle.bundle_stream')
-
-    bundle = BundleColumn(accessor='bundle', sortable=False)
-
-    test_run = TemplateColumn(
-        '<a href="{{ record.test_run.get_absolute_url }}">'
-        '<code>{{ record.test_run.test }} results<code/></a>',
-        accessor="test__test_id",
-        )
-
-    uploaded_on = TemplateColumn(
-        '{{ record.bundle.uploaded_on|date:"Y-m-d H:i:s" }}',
-        accessor='bundle__uploaded_on')
-
-    passes = Column(accessor='pass_count', sortable=False)
-    total = Column(accessor='result_count', sortable=False)
-    specific_results = SpecificCaseColumn(accessor='specific_results', sortable=False)
+            self.base_columns.insert(0, 'bundle', bundle_col)
+        if len(match_maker.filter_data['bundle_streams']) > 1:
+            self.base_columns.insert(0, 'bundle_stream', bundle_stream_col)
+        self.base_columns.insert(0, 'tag', tag_col)
+
+    tag = Column()
+
+    def render_bundle_stream(self, record):
+        bundle_streams = set(tr.bundle.bundle_stream for tr in record.test_runs)
+        links = []
+        for bs in sorted(bundle_streams, key=operator.attrgetter('pathname')):
+            links.append('<a href="%s">%s</a>' % (
+                bs.get_absolute_url(), escape(bs.pathname)))
+        return mark_safe('<br />'.join(links))
+    bundle_stream = Column(mark_safe("Bundle Stream(s)"))
+
+    def render_bundle(self, record):
+        bundles = set(tr.bundle for tr in record.test_runs)
+        links = []
+        for b in sorted(bundles, key=operator.attrgetter('uploaded_on')):
+            links.append('<a href="%s">%s</a>' % (
+                b.get_absolute_url(), escape(b.content_filename)))
+        return mark_safe('<br />'.join(links))
+    bundle = Column(mark_safe("Bundle(s)"))
+
+    def render_test_run(self, record):
+        # This column is only rendered if we don't really expect
+        # record.test_runs to be very long...
+        links = []
+        for tr in record.test_runs:
+            text = '%s / %s' % (tr.denormalization.count_pass, tr.denormalization.count_all())
+            links.append('<a href="%s">%s</a>' % (tr.get_absolute_url(), text))
+        return mark_safe('&nbsp;'.join(links))
+    test_run = Column("Results")
+
+    passes = Column(accessor='pass_count')
+    total = Column(accessor='result_count')
+
+    def render_specific_results(self, value, record):
+        r = []
+        for result in value:
+            if result.result == result.RESULT_PASS and result.units:
+                s = '%s %s' % (result.measurement, result.units)
+            else:
+                s = result.RESULT_MAP[result.result]
+            r.append('<a href="' + result.get_absolute_url() + '">'+s+'</a>')
+        return mark_safe(', '.join(r))
+    specific_results = Column()
+
     def get_queryset(self, user, filter):
         return filter.get_test_runs(user)
 
     datatable_opts = {
         "sPaginationType": "full_numbers",
         "iDisplayLength": 25,
+        "bSort": False,
         }