=== added file 'dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py'
@@ -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'
@@ -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'
@@ -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'
@@ -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'
@@ -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'
@@ -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(' '.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,
}