=== modified file 'dashboard_app/admin.py'
@@ -209,7 +209,6 @@
class TestRunFilterAttributeInline(admin.TabularInline):
model = TestRunFilterAttribute
inlines = [TestRunFilterAttributeInline]
- raw_id_fields = ['test_case']
save_as = True
=== added file 'dashboard_app/migrations/0022_auto__add_testrunfiltertest__add_testrunfiltertestcase__del_field_test.py'
@@ -0,0 +1,309 @@
+# -*- 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 model 'TestRunFilterTest'
+ db.create_table('dashboard_app_testrunfiltertest', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('test', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['dashboard_app.Test'])),
+ ('filter', self.gf('django.db.models.fields.related.ForeignKey')(related_name='tests', to=orm['dashboard_app.TestRunFilter'])),
+ ('index', self.gf('django.db.models.fields.PositiveIntegerField')()),
+ ))
+ db.send_create_signal('dashboard_app', ['TestRunFilterTest'])
+
+ # Adding model 'TestRunFilterTestCase'
+ db.create_table('dashboard_app_testrunfiltertestcase', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('test_case', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['dashboard_app.TestCase'])),
+ ('test', self.gf('django.db.models.fields.related.ForeignKey')(related_name='cases', to=orm['dashboard_app.TestRunFilterTest'])),
+ ('index', self.gf('django.db.models.fields.PositiveIntegerField')()),
+ ))
+ db.send_create_signal('dashboard_app', ['TestRunFilterTestCase'])
+
+ # Deleting field 'TestRunFilter.test'
+ db.delete_column('dashboard_app_testrunfilter', 'test_id')
+
+ # Deleting field 'TestRunFilter.test_case'
+ db.delete_column('dashboard_app_testrunfilter', 'test_case_id')
+
+
+ def backwards(self, orm):
+ # Deleting model 'TestRunFilterTest'
+ db.delete_table('dashboard_app_testrunfiltertest')
+
+ # Deleting model 'TestRunFilterTestCase'
+ db.delete_table('dashboard_app_testrunfiltertestcase')
+
+ # Adding field 'TestRunFilter.test'
+ db.add_column('dashboard_app_testrunfilter', 'test',
+ self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.Test'], null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'TestRunFilter.test_case'
+ db.add_column('dashboard_app_testrunfilter', 'test_case',
+ self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.TestCase'], null=True, blank=True),
+ keep_default=False)
+
+
+ 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'})
+ },
+ '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']"})
+ },
+ 'dashboard_app.testrunfiltertest': {
+ 'Meta': {'object_name': 'TestRunFilterTest'},
+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tests'", 'to': "orm['dashboard_app.TestRunFilter']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.Test']"})
+ },
+ 'dashboard_app.testrunfiltertestcase': {
+ 'Meta': {'object_name': 'TestRunFilterTestCase'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cases'", 'to': "orm['dashboard_app.TestRunFilterTest']"}),
+ 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.TestCase']"})
+ },
+ '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
=== modified file 'dashboard_app/models.py'
@@ -1579,16 +1579,16 @@
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_result(self, result):
+ prefix = result.test_case.test.test_id + ':' + result.test_case.test_case_id + ' '
+ if result.test_case.units:
+ return prefix + '%s%s' % (result.measurement, result.units)
+ else:
+ return prefix + result.RESULT_MAP[result.result]
- def _format_test_run(self, test, tr):
+ def _format_test_run(self, tr):
return "%s %s pass / %s total" % (
- test.test_id,
+ tr.test.test_id,
tr.denormalization.count_pass,
tr.denormalization.count_all())
@@ -1597,21 +1597,20 @@
def format_for_mail(self):
r = [' ~%s/%s ' % (self.filter.owner.username, self.filter.name)]
- if self.filter.test_case:
- r.append("%s:%s" % (
- self.filter.test.test_id,
- self.filter.test_case.test_case_id,
- ))
- r.append(' ' + ', '.join(
- self._format_test_result(self.filter.test_case, r)
- for r in self.specific_results))
- elif self.filter.test:
- 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:
+ if not self.filter_data['tests']:
r.append(self._format_many_test_runs())
+ else:
+ for test in self.filter_data['tests']:
+ if not test.all_case_ids():
+ for tr in self.test_runs:
+ if tr.test == test.test:
+ r.append('\n ')
+ r.append(self._format_test_run(tr))
+ for case_id in test.all_case_ids():
+ for result in self.specific_results:
+ if result.test_case.id == case_id:
+ r.append('\n ')
+ r.append(self._format_test_result(result))
r.append('\n')
return ''.join(r)
@@ -1634,10 +1633,6 @@
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):
test_run_ids = set()
@@ -1645,17 +1640,20 @@
test_run_ids.update(datum['id__arrayagg'])
r = []
trs = TestRun.objects.filter(id__in=test_run_ids).select_related(
- 'denormalization', 'bundle', 'bundle__bundle_stream')
+ 'denormalization', 'bundle', 'bundle__bundle_stream', 'test')
trs_by_id = {}
for tr in trs:
trs_by_id[tr.id] = tr
- if self.has_specific_results:
+ case_ids = set()
+ for t in self.filter_data['tests']:
+ case_ids.update(t.all_case_ids())
+ if case_ids:
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')
+ values = TestResult.objects.filter(
+ test_case__id__in=case_ids,
+ test_run__id__in=test_run_ids).values_list(
+ 'test_run__id', 'id')
result_ids = set()
for v in values:
result_ids_by_tr_id.setdefault(v[0], []).append(v[1])
@@ -1673,16 +1671,16 @@
rs.append(results_by_id[result_id])
for datum in data:
trs = []
- for id in datum['id__arrayagg']:
+ for id in set(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:
+ if case_ids:
match.specific_results = []
- for id in datum['id__arrayagg']:
- match.specific_results.extend(results_by_tr_id[id])
+ for id in set(datum['id__arrayagg']):
+ match.specific_results.extend(results_by_tr_id.get(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)
@@ -1718,6 +1716,34 @@
return '%s = %s' % (self.name, self.value)
+class TestRunFilterTest(models.Model):
+
+ test = models.ForeignKey(Test, related_name="+")
+ filter = models.ForeignKey("TestRunFilter", related_name="tests")
+ index = models.PositiveIntegerField(
+ help_text = _(u"The index of this test in the filter"))
+
+ def all_case_ids(self):
+ return self.cases.all().order_by('index').values_list('test_case__id', flat=True)
+
+ def all_case_names(self):
+ return self.cases.all().order_by('index').values_list('test_case__test_case_id', flat=True)
+
+ def __unicode__(self):
+ return unicode(self.test)
+
+
+class TestRunFilterTestCase(models.Model):
+
+ test_case = models.ForeignKey(TestCase, related_name="+")
+ test = models.ForeignKey(TestRunFilterTest, related_name="cases")
+ index = models.PositiveIntegerField(
+ help_text = _(u"The index of this case in the test"))
+
+ def __unicode__(self):
+ return unicode(self.test_case)
+
+
class SQLArrayAgg(SQLAggregate):
sql_function = 'array_agg'
@@ -1749,13 +1775,6 @@
bundle_streams = models.ManyToManyField(BundleStream)
bundle_streams.help_text = 'A filter only matches tests within the given <b>bundle streams</b>.'
- test = models.ForeignKey(
- Test, blank=True, null=True,
- help_text=("A filter can optionally be restricted to a particular "
- "<b>test</b>, or even a <b>test case</b> within a test."))
-
- test_case = models.ForeignKey(TestCase, blank=True, null=True)
-
public = models.BooleanField(
default=False, help_text="Whether other users can see this filter.")
@@ -1768,27 +1787,12 @@
return {
'bundle_streams': self.bundle_streams.all(),
'attributes': self.attributes.all().values_list('name', 'value'),
- 'test': self.test,
- 'test_case': self.test_case,
+ 'tests': self.tests.all().prefetch_related('cases'),
'build_number_attribute': self.build_number_attribute,
}
def __unicode__(self):
- test = self.test
- if not test:
- test = "<any>"
- test_case = self.test_case
- if not test_case:
- test_case = "<any>"
- attrs = []
- for attr in self.attributes.all():
- attrs.append(unicode(attr))
- attrs = ', '.join(attrs)
- if attrs:
- attrs = ' ' + attrs + '; '
- return "<TestRunFilter ~%s/%s %d streams;%s %s:%s>" % (
- self.owner.username, self.name, self.bundle_streams.count(), attrs, test, test_case)
-
+ return "<TestRunFilter ~%s/%s>" % (self.owner.username, self.name)
# given filter:
# select from testrun
@@ -1797,9 +1801,9 @@
# and testrun has attribute with key = key2 and value = value2
# and ...
# and testrun has attribute with key = keyN and value = valueN
- # and testrun has filter.test/testcase requested
+ # and testrun has any of the tests/testcases requested
- def get_test_runs_impl(self, user, bundle_streams, attributes):
+ def get_test_runs_impl(self, user, bundle_streams, attributes, tests):
accessible_bundle_streams = BundleStream.objects.accessible_by_principal(
user)
bs_ids = [bs.id for bs in set(accessible_bundle_streams) & set(bundle_streams)]
@@ -1815,12 +1819,21 @@
name=name, value=value, content_type_id=content_type_id
).values('object_id')))
- if self.test_case:
- conditions.append(models.Q(
- test_results__test_case=self.test_case,
- test=self.test_case.test))
- elif self.test:
- conditions.append(models.Q(test=self.test))
+ test_condition = None
+ for test in tests:
+ cases = list(test.all_case_ids())
+ if cases:
+ q = models.Q(
+ test__id=test.test.id,
+ test_results__test_case__id__in=cases)
+ else:
+ q = models.Q(test__id=test.test.id)
+ if test_condition:
+ test_condition = test_condition | q
+ else:
+ test_condition = q
+ if test_condition:
+ conditions.append(test_condition)
testruns = TestRun.objects.filter(*conditions)
@@ -1840,8 +1853,7 @@
filter_data = {
'bundle_streams': bundle_streams,
'attributes': attributes,
- 'test': self.test,
- 'test_case': self.test_case,
+ 'tests': tests,
'build_number_attribute': self.build_number_attribute,
}
@@ -1858,15 +1870,8 @@
@classmethod
def matches_against_bundle(self, bundle):
- filters = bundle.bundle_stream.testrunfilter_set.all()
- filters = filters.filter(
- models.Q(test__isnull=True)
- |models.Q(test__in=bundle.test_runs.all().values('test')))
- filters = filters.filter(
- models.Q(test_case__isnull=True)
- |models.Q(test_case__in=TestResult.objects.filter(
- test_run__in=bundle.test_runs.all()).values('test_case')))
- filters = filters.extra(
+ bundle_filters = bundle.bundle_stream.testrunfilter_set.all()
+ attribute_filters = list(bundle_filters.extra(
where=[
"""(select min((select count(*)
from dashboard_app_testrunfilterattribute
@@ -1878,8 +1883,23 @@
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],
+ ))
+ no_test_filters = []#list(attribute_filters.annotate(models.Count('tests')).filter(tests__count=0))
+ no_test_case_filters = list(
+ TestRunFilter.objects.filter(
+ id__in=TestRunFilterTest.objects.filter(
+ filter__in=attribute_filters, test__in=bundle.test_runs.all().values('test_id')).annotate(
+ models.Count('cases')).filter(cases__count=0).values('filter__id'),
+ ))
+ tcf = TestRunFilter.objects.filter(
+ id__in=TestRunFilterTest.objects.filter(
+ filter__in=attribute_filters,
+ cases__test_case__id__in=bundle.test_runs.all().values('test_results__test_case__id')
+ ).values('filter__id')
)
- filters = list(filters)
+ test_case_filters = list(tcf)
+
+ filters = set(test_case_filters + no_test_case_filters + no_test_filters)
matches = []
bundle_with_counts = Bundle.objects.annotate(
pass_count=models.Sum('test_runs__denormalization__count_pass'),
@@ -1888,29 +1908,26 @@
fail_count=models.Sum('test_runs__denormalization__count_fail')).get(
id=bundle.id)
for filter in filters:
- if filter.test:
- 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_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)
+ match = FilterMatch()
+ match.filter = filter
+ match.filter_data = filter.summary_data
+ match.test_runs = list(bundle.test_runs.all())
+ match.specific_results = list(
+ TestResult.objects.filter(
+ test_case__id__in=filter.tests.all().values('cases__test_case__id'),
+ test_run__bundle=bundle))
+ 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)
return matches
def get_test_runs(self, user):
return self.get_test_runs_impl(
user,
self.bundle_streams.all(),
- self.attributes.values_list('name', 'value'))
+ self.attributes.values_list('name', 'value'),
+ self.tests.all())
@models.permalink
def get_absolute_url(self):
@@ -1957,8 +1974,27 @@
recipients = {}
for sub in subscriptions:
match = matches_by_filter_id[sub.filter.id]
- if sub.level == cls.NOTIFICATION_FAILURE and match.pass_count == match.result_count:
- continue
+ if sub.level == cls.NOTIFICATION_FAILURE:
+ failure_found = False
+ if not match.filter_data['tests']:
+ failure_found = match.pass_count != match.result_count
+ else:
+ for t in match.filter_data['tests']:
+ if not t.all_case_ids():
+ for tr in match.test_runs:
+ if tr.test == t.test:
+ if tr.denormalization.count_pass != tr.denormalization.count_all():
+ failure_found = True
+ break
+ if failure_found:
+ break
+ if not failure_found:
+ for r in match.specific_results:
+ if r.result != TestResult.RESULT_PASS:
+ failure_found = True
+ break
+ if not failure_found:
+ continue
recipients.setdefault(sub.user, []).append(match)
return recipients
=== modified file 'dashboard_app/static/css/filter-edit.css'
@@ -8,3 +8,13 @@
border: 1px solid rgb(204, 204, 204);
border-top: none;
}
+td.test-cell {
+ vertical-align: top;
+}
+table.test-case-formset {
+ border-collapse: collapse;
+}
+table.test-case-formset td {
+ padding-top: 0;
+ padding-bottom: 0;
+}
\ No newline at end of file
=== modified file 'dashboard_app/static/js/filter-edit.js'
@@ -1,28 +1,35 @@
$(function () {
function updateTestCasesFromTest() {
- var test_id=$("#id_test option:selected").html();
- var select = $("#id_test_case");
- select.empty();
- select.append(Option("<any>", ""));
- if (test_id != '<any>') {
- $.ajax(
- {
- url: test_case_url + test_id,
- dataType: 'json',
- success: function (data) {
- $(data).each(
- function (index, val) {
- select.append(Option(val.test_case_id, val.id));
- });
- select.removeAttr("disabled");
- }
- });
- } else {
- select.attr('disabled', 'disabled');
- }
+ var test_id=$(this).find("option:selected").html();
+ var selects = $(this).closest('tr').find('.test-case-formset select');
+ selects.each(
+ function () {
+ $(this).empty();
+ });
+ $.ajax(
+ {
+ url: test_case_url + test_id,
+ dataType: 'json',
+ success: function (data) {
+ selects.each(
+ function () {
+ var select = $(this);
+ $(data).each(
+ function (index, val) {
+ var test_case_id = val.test_case_id;
+ if (test_case_id.length > 50) {
+ test_case_id = test_case_id.substring(0, 50) + "...";
+ }
+ select.append(new Option(test_case_id, val.id));
+ });
+ select.removeAttr("disabled");
+ });
+ }
+ });
};
-$("#id_test").change(updateTestCasesFromTest);
+$("#id_tests_empty_form .test-case-formset-empty select").attr('disabled', 'disabled');
+$(".test-cell select").change(updateTestCasesFromTest);
var nameAutocompleteConfig = {
source: attr_name_completion_url
@@ -51,6 +58,7 @@
{
formTemplate: '#id_attributes_empty_form',
prefix: "attributes",
+ formCssClass: "attributes-dynamic-form",
addText: "Add a required attribute",
added: function(row) {
row.find(".name input").unbind();
@@ -60,4 +68,66 @@
}
});
+var formsetCallCount = 0;
+
+function formsetTestCase(test_row) {
+ var addText;
+ if (test_row.find(".test-case-formset select").size() < 2) {
+ addText = 'Specify test cases';
+ } else {
+ addText = 'Add another test case';
+ test_row.find('> td:last').hide();
+ }
+
+ var index = test_row.parent().children('.test-dynamic-form').index(test_row);
+
+ var fs = test_row.find(".test-case-formset > tbody > tr").formset(
+ {
+ formTemplate: test_row.find(".test-case-formset-empty"),
+ formCssClass: "test-cases-dynamic-form-" + formsetCallCount,
+ addText: addText,
+ deleteText: "Remove test case",
+ prefix: "tests-" + index,
+ added: function (row2) {
+ test_row.find('.add-row').text('Add another test case');
+ test_row.find('> td:last').hide();
+ },
+ removed: function (row2) {
+ if (test_row.find(".test-case-formset select").size() < 2) {
+ test_row.find('.add-row').text("Specify test cases");
+ test_row.find('> td:last').show();
+ }
+ }
+ }
+ );
+
+ test_row.data('formset', fs);
+
+ formsetCallCount += 1;
+}
+
+$("#tests-table > tbody > tr").formset(
+ {
+ formTemplate: '#id_tests_empty_form',
+ prefix: "tests",
+ formCssClass: "test-dynamic-form",
+ addText: "Add a test",
+ deleteText: "Remove test",
+ added: formsetTestCase,
+ removed: function () {
+ $("#tests-table > tbody > tr.test-dynamic-form").each(
+ function () {
+ var index = $(this).parent().children('.test-dynamic-form').index($(this));
+ $(this).data('formset').data('options').prefix = 'tests-' + index;
+ });
+ }
+ }
+);
+
+$("#tests-table > tbody > tr").each(
+ function () {
+ formsetTestCase($(this));
+ }
+);
+
});
\ No newline at end of file
=== modified file 'dashboard_app/static/js/jquery.formset.js'
@@ -115,10 +115,12 @@
}
if (hasChildElements(row)) {
row.addClass(options.formCssClass);
- if (row.is(':visible')) {
+// XXX mwhudson 2012-09-13: not sure what this check is for, doesn't
+// work well when whole form is hidden though...
+// if (row.is(':visible')) {
insertDeleteLink(row);
applyExtraClasses(row, i);
- }
+// }
}
});
@@ -152,6 +154,7 @@
}
// FIXME: Perhaps using $.data would be a better idea?
options.formTemplate = template;
+ $$.data('options', options);
if ($$.attr('tagName') == 'TR') {
// If forms are laid out as table rows, insert the
=== modified file 'dashboard_app/templates/dashboard_app/filter_form.html'
@@ -85,17 +85,40 @@
</span>
</dd>
<dt>
- Test and test case:
+ Tests and test cases:
</dt>
<dd>
- {{ form.test.errors }}
- {{ form.test_case.errors }}
- {{ form.test }}
- {{ form.test_case }}
- <br />
- <span class="helptext">
- A filter can optionally be restricted to a particular <b>test</b>, or even a <b>test case</b>
- within a test.
- </span>
+ {% with form.tests_formset as formset %}
+ <table id="tests-table">
+ <thead>
+ <tr>
+ <th>
+ Test
+ </th>
+ <th>
+ Test Cases
+ </th>
+ </tr>
+ </thead>
+ {{ formset.management_form }}
+ <tbody>
+ {% for form in formset %}
+ <tr>
+ {% include "dashboard_app/filter_form_test.html" %}
+ </tr>
+ {% empty %}
+ <tr>
+ </tr>
+ {% endfor %}
+ </tbody>
+ <tfoot>
+ {% with formset.empty_form as form %}
+ <tr style="display:none" id="id_tests_empty_form">
+ {% include "dashboard_app/filter_form_test.html" %}
+ </tr>
+ {% endwith %}
+ </tfoot>
+ </table>
+ {% endwith %}
</dd>
</dl>
=== added file 'dashboard_app/templates/dashboard_app/filter_form_test.html'
@@ -0,0 +1,37 @@
+ <td class="test-cell">
+ {{ form.test.errors }}
+ {{ form.test }}
+ </td>
+ <td>
+ <table class="test-case-formset">
+ {{ form.test_case_formset.management_form }}
+ <tbody>
+ {% for form in form.test_case_formset %}
+ <tr>
+ <td>
+ {{ form.test_case.errors }}
+ {{ form.test_case }}
+ </td>
+ <td>
+ </td>
+ </tr>
+ {% empty %}
+ <tr>
+ </tr>
+ {% endfor %}
+ </tbody>
+ <tfoot>
+ {% with form.test_case_formset as formset %}
+ <tr style="display:none" class="test-case-formset-empty">
+ <td>
+ {{ formset.empty_form.test_case }}
+ </td>
+ <td>
+ </td>
+ </tr>
+ {% endwith %}
+ </tfoot>
+ </table>
+ </td>
+ <td>
+ </td>
=== added file 'dashboard_app/templates/dashboard_app/filter_results_table.html'
@@ -0,0 +1,28 @@
+{% extends "ajax_table.html" %}
+
+{% block table.thead %}
+{% if table.complex_header %}
+<thead>
+ <tr>
+ {% for column in table.columns %}
+ {% if not column.column.in_group %}
+ <th {{ column.attrs.th.as_html }} rowspan="2">{{ column.header }}</th>
+ {% else %}
+ {% if column.column.first_in_group %}
+ <th class="ui-state-default" colspan="{{ column.column.group_length }}">{{ column.column.group_name }}</th>
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+ </tr>
+ <tr>
+ {% for column in table.columns %}
+ {% if column.column.in_group %}
+ <th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
+ {% endif %}
+ {% endfor %}
+ </tr>
+</thead>
+{% else %}
+{{ block.super }}
+{% endif %}
+{% endblock table.thead %}
=== modified file 'dashboard_app/templates/dashboard_app/filter_summary.html'
@@ -33,16 +33,27 @@
{% endif %}
<tr>
<th>
- Test case
+ Test cases
</th>
<td>
- {% if summary_data.test_case %}
- {{ summary_data.test }}:{{ summary_data.test_case }}
- {% elif summary_data.test %}
- {{ summary_data.test }}:<any>
- {% else %}
- <any>:<any>
- {% endif %}
+ <table>
+ <tbody>
+ {% for test in summary_data.tests %}
+ <tr>
+ <td>
+ {{ test.test }}
+ </td>
+ <td>
+ {% for test_case in test.all_case_names %}
+ {{ test_case }}
+ {% empty %}
+ <i>any</i>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
</td>
</tr>
</table>
=== modified file 'dashboard_app/views.py'
@@ -34,7 +34,8 @@
from django.db.models.manager import Manager
from django.db.models.query import QuerySet
from django import forms
-from django.forms.formsets import formset_factory
+from django.forms.formsets import BaseFormSet, formset_factory
+from django.forms.widgets import Select
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect, get_object_or_404
from django.template import RequestContext, loader
@@ -472,13 +473,24 @@
''')
test = TemplateColumn('''
- {% if record.test_case %}
- {{ record.test }}:{{ record.test_case }}
- {% elif record.test %}
- {{ record.test }}:<any>
- {% else %}
- <any>:<any>
- {% endif %}
+ <table style="border-collapse: collapse">
+ <tbody>
+ {% for test in record.tests.all %}
+ <tr>
+ <td>
+ {{ test.test }}
+ </td>
+ <td>
+ {% for test_case in test.all_case_names %}
+ {{ test_case }}
+ {% empty %}
+ <i>any</i>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
''')
subscription = Column()
@@ -532,10 +544,27 @@
)
+class TestRunColumn(Column):
+ def render(self, record):
+ # This column is only rendered if we don't really expect
+ # record.test_runs to be very long...
+ links = []
+ trs = [tr for tr in record.test_runs if tr.test.test_id == self.verbose_name]
+ for tr in trs:
+ 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))
+
+
class SpecificCaseColumn(Column):
- def render(self, value, record):
+ def __init__(self, verbose_name, test_case_id):
+ super(SpecificCaseColumn, self).__init__(verbose_name)
+ self.test_case_id = test_case_id
+ def render(self, record):
r = []
- for result in value:
+ for result in record.specific_results:
+ if result.test_case_id != self.test_case_id:
+ continue
if result.result == result.RESULT_PASS and result.units:
s = '%s %s' % (result.measurement, result.units)
else:
@@ -551,28 +580,38 @@
class FilterTable(DataTablesTable):
def __init__(self, *args, **kwargs):
+ kwargs['template'] = 'dashboard_app/filter_results_table.html'
super(FilterTable, self).__init__(*args, **kwargs)
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)
+ self.complex_header = False
+ if match_maker.filter_data['tests']:
+ del self.base_columns['passes']
+ del self.base_columns['total']
+ for i, t in enumerate(reversed(match_maker.filter_data['tests'])):
+ if len(t.all_case_names()) == 0:
+ col = TestRunColumn(mark_safe(t.test.test_id))
+ self.base_columns.insert(0, 'test_run_%s' % i, col)
+ elif len(t.all_case_names()) == 1:
+ n = t.test.test_id + ':' + t.all_case_names()[0]
+ col = SpecificCaseColumn(mark_safe(n), t.all_case_ids()[0])
+ self.base_columns.insert(0, 'test_run_%s_case' % i, col)
+ else:
+ col0 = SpecificCaseColumn(mark_safe(t.all_case_names()[0]), t.all_case_ids()[0])
+ col0.in_group = True
+ col0.first_in_group = True
+ col0.group_length = len(t.all_case_names())
+ col0.group_name = mark_safe(t.test.test_id)
+ self.complex_header = True
+ self.base_columns.insert(0, 'test_run_%s_case_%s' % (i, 0), col0)
+ for j, n in enumerate(t.all_case_names()[1:], 1):
+ col = SpecificCaseColumn(mark_safe(n), t.all_case_ids()[j])
+ col.in_group = True
+ col.first_in_group = False
+ self.base_columns.insert(j, 'test_run_%s_case_%s' % (i, j), col)
else:
self.base_columns.insert(0, 'bundle', bundle_col)
if len(match_maker.filter_data['bundle_streams']) > 1:
@@ -599,30 +638,9 @@
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)
@@ -751,6 +769,97 @@
AttributesFormSet = formset_factory(AttributesForm, extra=0)
+
+
+class TruncatingSelect(Select):
+
+ def render_option(self, selected_choices, option_value, option_label):
+ if len(option_label) > 50:
+ option_label = option_label[:50] + '...'
+ return super(TruncatingSelect, self).render_option(
+ selected_choices, option_value, option_label)
+
+
+class TRFTestCaseForm(forms.Form):
+
+ test_case = forms.ModelChoiceField(
+ queryset=TestCase.objects.none(), widget=TruncatingSelect, empty_label=None)
+
+
+class BaseTRFTestCaseFormSet(BaseFormSet):
+
+ def __init__(self, *args, **kw):
+ self._queryset = kw.pop('queryset')
+ super(BaseTRFTestCaseFormSet, self).__init__(*args, **kw)
+
+ def add_fields(self, form, index):
+ super(BaseTRFTestCaseFormSet, self).add_fields(form, index)
+ if self._queryset is not None:
+ form.fields['test_case'].queryset = self._queryset
+
+
+TRFTestCaseFormSet = formset_factory(
+ TRFTestCaseForm, extra=0, formset=BaseTRFTestCaseFormSet)
+
+
+class TRFTestForm(forms.Form):
+
+ def __init__(self, *args, **kw):
+ super(TRFTestForm, self).__init__(*args, **kw)
+ kw['initial'] = kw.get('initial', {}).get('test_cases', None)
+ kw.pop('empty_permitted', None)
+ kw['queryset'] = None
+ v = self['test'].value()
+ if v:
+ test = self.fields['test'].to_python(v)
+ queryset = TestCase.objects.filter(test=test).order_by('test_case_id')
+ kw['queryset'] = queryset
+ self.test_case_formset = TRFTestCaseFormSet(*args, **kw)
+
+ def is_valid(self):
+ return super(TRFTestForm, self).is_valid() and \
+ self.test_case_formset.is_valid()
+
+ def full_clean(self):
+ super(TRFTestForm, self).full_clean()
+ self.test_case_formset.full_clean()
+
+ test = forms.ModelChoiceField(
+ queryset=Test.objects.order_by('test_id'), required=True)
+
+
+class BaseTRFTestsFormSet(BaseFormSet):
+
+ def is_valid(self):
+ if not super(BaseTRFTestsFormSet, self).is_valid():
+ return False
+ for form in self.forms:
+ if not form.is_valid():
+ return False
+ return True
+
+
+TRFTestsFormSet = formset_factory(
+ TRFTestForm, extra=0, formset=BaseTRFTestsFormSet)
+
+
+class FakeTRFTest(object):
+ def __init__(self, form):
+ self.test = form.cleaned_data['test']
+ self.test_id = self.test.id
+ self._case_ids = []
+ self._case_names = []
+ for tc_form in form.test_case_formset:
+ self._case_ids.append(tc_form.cleaned_data['test_case'].id)
+ self._case_names.append(tc_form.cleaned_data['test_case'].test_case_id)
+
+ def all_case_ids(self):
+ return self._case_ids
+
+ def all_case_names(self):
+ return self._case_names
+
+
class TestRunFilterForm(forms.ModelForm):
class Meta:
model = TestRunFilter
@@ -766,12 +875,6 @@
Context({'STATIC_URL': settings.STATIC_URL})
)) + super_media
- test = forms.ModelChoiceField(
- queryset=Test.objects.order_by('test_id'), empty_label="<any>", required=False)
-
- test_case = forms.ModelChoiceField(
- queryset=TestCase.objects.none(), empty_label="<any>", required=False)
-
def validate_name(self, value):
self.instance.name = value
try:
@@ -789,23 +892,42 @@
instance.attributes.all().delete()
for a in self.attributes_formset.cleaned_data:
instance.attributes.create(name=a['name'], value=a['value'])
+ instance.tests.all().delete()
+ for i, test_form in enumerate(self.tests_formset.forms):
+ trf_test = instance.tests.create(
+ test=test_form.cleaned_data['test'], index=i)
+ for j, test_case_form in enumerate(test_form.test_case_formset.forms):
+ trf_test.cases.create(
+ test_case=test_case_form.cleaned_data['test_case'], index=j)
return instance
def is_valid(self):
return super(TestRunFilterForm, self).is_valid() and \
- self.attributes_formset.is_valid()
+ self.attributes_formset.is_valid() and \
+ self.tests_formset.is_valid()
+
+ def full_clean(self):
+ super(TestRunFilterForm, self).full_clean()
+ self.attributes_formset.full_clean()
+ self.tests_formset.full_clean()
@property
def summary_data(self):
data = self.cleaned_data.copy()
+ tests = []
+ for form in self.tests_formset.forms:
+ tests.append(FakeTRFTest(form))
data['attributes'] = [
(d['name'], d['value']) for d in self.attributes_formset.cleaned_data]
+ data['tests'] = tests
return data
def __init__(self, user, *args, **kwargs):
super(TestRunFilterForm, self).__init__(*args, **kwargs)
self.instance.owner = user
kwargs.pop('instance', None)
+
+ attr_set_args = kwargs.copy()
if self.instance.pk:
initial = []
for attr in self.instance.attributes.all():
@@ -813,24 +935,34 @@
'name': attr.name,
'value': attr.value,
})
- kwargs['initial'] = initial
- kwargs['prefix'] = 'attributes'
- self.attributes_formset = AttributesFormSet(*args, **kwargs)
+ attr_set_args['initial'] = initial
+ attr_set_args['prefix'] = 'attributes'
+ self.attributes_formset = AttributesFormSet(*args, **attr_set_args)
+
+ tests_set_args = kwargs.copy()
+ if self.instance.pk:
+ initial = []
+ for test in self.instance.tests.all().order_by('index').prefetch_related('cases'):
+ initial.append({
+ 'test': test.test,
+ 'test_cases': [{'test_case': unicode(tc.test_case.id)} for tc in test.cases.all().order_by('index')],
+ })
+ tests_set_args['initial'] = initial
+ tests_set_args['prefix'] = 'tests'
+ self.tests_formset = TRFTestsFormSet(*args, **tests_set_args)
+
self.fields['bundle_streams'].queryset = \
BundleStream.objects.accessible_by_principal(user).order_by('pathname')
self.fields['name'].validators.append(self.validate_name)
- test = self['test'].value()
- if test:
- if not isinstance(test, int):
- test = int(repr(test)[2:-1])
- test = Test.objects.get(pk=test)
- self.fields['test_case'].queryset = TestCase.objects.filter(test=test).order_by('test_case_id')
def get_test_runs(self, user):
assert self.is_valid(), self.errors
filter = self.save(commit=False)
+ tests = []
+ for form in self.tests_formset.forms:
+ tests.append(FakeTRFTest(form))
return filter.get_test_runs_impl(
- user, self.cleaned_data['bundle_streams'], self.summary_data['attributes'])
+ user, self.cleaned_data['bundle_streams'], self.summary_data['attributes'], tests)
def filter_form(request, bread_crumb_trail, instance=None):