From patchwork Sun Sep 23 22:24:09 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael-Doyle Hudson X-Patchwork-Id: 11642 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id BE6EF23EFF for ; Sun, 23 Sep 2012 22:24:15 +0000 (UTC) Received: from mail-ie0-f180.google.com (mail-ie0-f180.google.com [209.85.223.180]) by fiordland.canonical.com (Postfix) with ESMTP id F3019A1859F for ; Sun, 23 Sep 2012 22:24:14 +0000 (UTC) Received: by ieje10 with SMTP id e10so8619166iej.11 for ; Sun, 23 Sep 2012 15:24:14 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf :content-type:mime-version:x-launchpad-project:x-launchpad-branch :x-launchpad-message-rationale:x-launchpad-branch-revision-number :x-launchpad-notification-type:to:from:subject:message-id:date :reply-to:sender:errors-to:precedence:x-generated-by :x-launchpad-hash:x-gm-message-state; bh=z61TG3RV4+j9MRdpcUJm0cxiX11T0fj9T02kWeonUD0=; b=E25uRXEqq2oXSqce8hkgd49a/Vx7EzXxAqcMmml7k1HXYdvuV2hPkVygJx9cJwkxS+ x4lx7PW02ckXRthfIxZ+eSsCP84rgoLLRO9+ZbaBZXqhFn1yyBgnKvWJIVeTubkTIQix ftXaucf0AyVIrLmvkHstMBuULKoWg47rtlvliskRB9UHroAyjE+jpNPcBV1yHglue8d2 Q+mOBx1GGKwKWKwWtY0g/Jyj7KQdMG+CEisv9TSg9hqP6DiX/hc5AIPykeW7mxNJRYU2 kWDWzFrMy2SHbsGtWw9cXES7L3KWXT7EqfQ5/hjq2x7V9RQr3Hq6Qf3EFwRBgUHmPBuw NheQ== Received: by 10.50.184.129 with SMTP id eu1mr3804123igc.0.1348439054251; Sun, 23 Sep 2012 15:24:14 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.50.184.232 with SMTP id ex8csp217596igc; Sun, 23 Sep 2012 15:24:12 -0700 (PDT) Received: by 10.180.77.34 with SMTP id p2mr10174408wiw.0.1348439051921; Sun, 23 Sep 2012 15:24:11 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id cl2si13890868wib.32.2012.09.23.15.24.10 (version=TLSv1/SSLv3 cipher=OTHER); Sun, 23 Sep 2012 15:24:11 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) client-ip=91.189.90.7; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) smtp.mail=bounces@canonical.com Received: from ackee.canonical.com ([91.189.89.26]) by indium.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1TFuav-00061Y-VZ for ; Sun, 23 Sep 2012 22:24:09 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id D6FF5E03F3 for ; Sun, 23 Sep 2012 22:24:09 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: lava-dashboard X-Launchpad-Branch: ~linaro-validation/lava-dashboard/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 343 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-dashboard/trunk] Rev 343: allow filters to match multiple tests and test cases Message-Id: <20120923222409.16000.11757.launchpad@ackee.canonical.com> Date: Sun, 23 Sep 2012 22:24:09 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="15985"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 5ee09285de97553113fbb1a1893e151df82cc0db X-Gm-Message-State: ALoCoQk+wTLIw6O+maYcqvULVKp8h02EqGXblZ3o5ov3B1cZLqlYq8Mdxv7af8aMc1Ug82nqjmlN Merge authors: Michael Hudson-Doyle (mwhudson) Related merge proposals: https://code.launchpad.net/~mwhudson/lava-dashboard/trf-multiple-test-cases/+merge/125385 proposed by: Michael Hudson-Doyle (mwhudson) review: Approve - Andy Doan (doanac) ------------------------------------------------------------ revno: 343 [merge] committer: Michael Hudson-Doyle branch nick: trunk timestamp: Mon 2012-09-24 10:09:37 +1200 message: allow filters to match multiple tests and test cases added: dashboard_app/migrations/0022_auto__add_testrunfiltertest__add_testrunfiltertestcase__del_field_test.py dashboard_app/templates/dashboard_app/filter_form_test.html dashboard_app/templates/dashboard_app/filter_results_table.html modified: dashboard_app/admin.py dashboard_app/models.py dashboard_app/static/css/filter-edit.css dashboard_app/static/js/filter-edit.js dashboard_app/static/js/jquery.formset.js dashboard_app/templates/dashboard_app/filter_form.html dashboard_app/templates/dashboard_app/filter_summary.html dashboard_app/views.py --- lp:lava-dashboard https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk You are subscribed to branch lp:lava-dashboard. To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk/+edit-subscription === modified file 'dashboard_app/admin.py' --- dashboard_app/admin.py 2012-08-17 02:05:39 +0000 +++ dashboard_app/admin.py 2012-09-12 01:45:09 +0000 @@ -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' --- dashboard_app/migrations/0022_auto__add_testrunfiltertest__add_testrunfiltertestcase__del_field_test.py 1970-01-01 00:00:00 +0000 +++ dashboard_app/migrations/0022_auto__add_testrunfiltertest__add_testrunfiltertestcase__del_field_test.py 2012-09-12 01:16:05 +0000 @@ -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' --- dashboard_app/models.py 2012-09-06 21:24:39 +0000 +++ dashboard_app/models.py 2012-09-19 03:06:08 +0000 @@ -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 bundle streams.' - test = models.ForeignKey( - Test, blank=True, null=True, - help_text=("A filter can optionally be restricted to a particular " - "test, or even a test case 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 = "" - test_case = self.test_case - if not test_case: - test_case = "" - attrs = [] - for attr in self.attributes.all(): - attrs.append(unicode(attr)) - attrs = ', '.join(attrs) - if attrs: - attrs = ' ' + attrs + '; ' - return "" % ( - self.owner.username, self.name, self.bundle_streams.count(), attrs, test, test_case) - + return "" % (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' --- dashboard_app/static/css/filter-edit.css 2012-08-14 02:25:44 +0000 +++ dashboard_app/static/css/filter-edit.css 2012-09-13 03:16:48 +0000 @@ -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' --- dashboard_app/static/js/filter-edit.js 2012-09-09 23:30:23 +0000 +++ dashboard_app/static/js/filter-edit.js 2012-09-20 22:17:38 +0000 @@ -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("", "")); - 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' --- dashboard_app/static/js/jquery.formset.js 2012-09-07 00:53:52 +0000 +++ dashboard_app/static/js/jquery.formset.js 2012-09-19 04:02:42 +0000 @@ -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' --- dashboard_app/templates/dashboard_app/filter_form.html 2012-09-09 23:30:23 +0000 +++ dashboard_app/templates/dashboard_app/filter_form.html 2012-09-19 22:56:03 +0000 @@ -85,17 +85,40 @@
- Test and test case: + Tests and test cases:
- {{ form.test.errors }} - {{ form.test_case.errors }} - {{ form.test }} - {{ form.test_case }} -
- - A filter can optionally be restricted to a particular test, or even a test case - within a test. - + {% with form.tests_formset as formset %} + + + + + + + + {{ formset.management_form }} + + {% for form in formset %} + + {% include "dashboard_app/filter_form_test.html" %} + + {% empty %} + + + {% endfor %} + + + {% with formset.empty_form as form %} + + {% include "dashboard_app/filter_form_test.html" %} + + {% endwith %} + +
+ Test + + Test Cases +
+ {% endwith %}
=== added file 'dashboard_app/templates/dashboard_app/filter_form_test.html' --- dashboard_app/templates/dashboard_app/filter_form_test.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_form_test.html 2012-09-19 22:56:03 +0000 @@ -0,0 +1,37 @@ + + {{ form.test.errors }} + {{ form.test }} + + + + {{ form.test_case_formset.management_form }} + + {% for form in form.test_case_formset %} + + + + + {% empty %} + + + {% endfor %} + + + {% with form.test_case_formset as formset %} + + + + + {% endwith %} + +
+ {{ form.test_case.errors }} + {{ form.test_case }} + +
+ + + === added file 'dashboard_app/templates/dashboard_app/filter_results_table.html' --- dashboard_app/templates/dashboard_app/filter_results_table.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_results_table.html 2012-09-14 03:14:43 +0000 @@ -0,0 +1,28 @@ +{% extends "ajax_table.html" %} + +{% block table.thead %} +{% if table.complex_header %} + + + {% for column in table.columns %} + {% if not column.column.in_group %} + {{ column.header }} + {% else %} + {% if column.column.first_in_group %} + {{ column.column.group_name }} + {% endif %} + {% endif %} + {% endfor %} + + + {% for column in table.columns %} + {% if column.column.in_group %} + {{ column.header }} + {% endif %} + {% endfor %} + + +{% else %} +{{ block.super }} +{% endif %} +{% endblock table.thead %} === modified file 'dashboard_app/templates/dashboard_app/filter_summary.html' --- dashboard_app/templates/dashboard_app/filter_summary.html 2012-09-05 02:59:43 +0000 +++ dashboard_app/templates/dashboard_app/filter_summary.html 2012-09-13 04:28:04 +0000 @@ -33,16 +33,27 @@ {% endif %} - Test case + Test cases - {% if summary_data.test_case %} - {{ summary_data.test }}:{{ summary_data.test_case }} - {% elif summary_data.test %} - {{ summary_data.test }}:<any> - {% else %} - <any>:<any> - {% endif %} + + + {% for test in summary_data.tests %} + + + + + {% endfor %} + +
+ {{ test.test }} + + {% for test_case in test.all_case_names %} + {{ test_case }} + {% empty %} + any + {% endfor %} +
=== modified file 'dashboard_app/views.py' --- dashboard_app/views.py 2012-09-09 23:13:10 +0000 +++ dashboard_app/views.py 2012-09-19 22:56:03 +0000 @@ -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 %} + + + {% for test in record.tests.all %} + + + + + {% endfor %} + +
+ {{ test.test }} + + {% for test_case in test.all_case_names %} + {{ test_case }} + {% empty %} + any + {% endfor %} +
''') 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('%s' % (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('
'.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('%s' % (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(''+s+'') - 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="", required=False) - - test_case = forms.ModelChoiceField( - queryset=TestCase.objects.none(), empty_label="", 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):