From patchwork Sun Sep 9 23:07:13 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: 11256 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 3719B23E54 for ; Sun, 9 Sep 2012 23:07:17 +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 4F23CA19179 for ; Sun, 9 Sep 2012 23:07:16 +0000 (UTC) Received: by ieak11 with SMTP id k11so2006654iea.11 for ; Sun, 09 Sep 2012 16:07:15 -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=tRjR3jr3PDKIgCqO2UTx+dCXruaT8GQzsJcRFkyc2FI=; b=Scx1XBFZrsEhZOKT0EJF3LrPfRpJarTard9mKKTruvmICepWuTqBqiMqRqrrY1vNBE hy0lMZwMk+F7crNJfUGd5ZswW/hpRF0EvA6bxky5Kf/yplsicomnF1b92Fl4bKVe8zbB MBqB11OyQnMTtMe/5zS/M4Acbbph9WD1WzcDaShrWH9sDivxdFRslSil3WT0YjeZ1wEN TKPBaTeSXbhSpFEAuh+ZN8fwdWt63T2K2LF33IfF7Qqzl2RMQ9igqqITckHhZLIzGeo2 VoM5p7WWEZtpRasEhm+4frJrvhUdSaQEBUu2XakU6q4HiiU+qRVMGn6v+vI3OVYPjVTB MpIg== Received: by 10.42.84.69 with SMTP id k5mr15324813icl.5.1347232035757; Sun, 09 Sep 2012 16:07:15 -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 ex8csp64783igc; Sun, 9 Sep 2012 16:07:14 -0700 (PDT) Received: by 10.180.97.33 with SMTP id dx1mr12718933wib.18.1347232033974; Sun, 09 Sep 2012 16:07:13 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id h21si16391373wea.103.2012.09.09.16.07.13 (version=TLSv1/SSLv3 cipher=OTHER); Sun, 09 Sep 2012 16:07:13 -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 1TAqav-00032z-4r for ; Sun, 09 Sep 2012 23:07:13 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id 13022E0411 for ; Sun, 9 Sep 2012 23:07:13 +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: 341 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-dashboard/trunk] Rev 341: add the ability to group and order filter matches by a build number Message-Id: <20120909230713.6896.92868.launchpad@ackee.canonical.com> Date: Sun, 09 Sep 2012 23:07:13 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="15914"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 151f71bdff7772af94b5be8dfe91cfa656f0f414 X-Gm-Message-State: ALoCoQl0C2CfP4fSE5S48pLD9Bv9Iu9rSz0tGbMDrPSXUypl4CyVx0y+FJ0QvZyEmzyhqYlIFl/Z Merge authors: Michael Hudson-Doyle (mwhudson) Related merge proposals: https://code.launchpad.net/~mwhudson/lava-dashboard/trf-order-by-group-by-attribute/+merge/122996 proposed by: Michael Hudson-Doyle (mwhudson) ------------------------------------------------------------ revno: 341 [merge] committer: Michael Hudson-Doyle branch nick: trunk timestamp: Mon 2012-09-10 11:05:58 +1200 message: add the ability to group and order filter matches by a build number added: dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py dashboard_app/migrations/0021_add_cast_integer.py modified: dashboard_app/models.py dashboard_app/templates/dashboard_app/filter_form.html dashboard_app/templates/dashboard_app/filter_summary.html dashboard_app/views.py --- lp:lava-dashboard https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk You are subscribed to branch lp:lava-dashboard. To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk/+edit-subscription === added file 'dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py' --- dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py 1970-01-01 00:00:00 +0000 +++ dashboard_app/migrations/0020_auto__add_field_testrunfilter_build_number_attribute.py 2012-09-04 00:37:05 +0000 @@ -0,0 +1,265 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'TestRunFilter.build_number_attribute' + db.add_column('dashboard_app_testrunfilter', 'build_number_attribute', + self.gf('django.db.models.fields.CharField')(max_length=1024, null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'TestRunFilter.build_number_attribute' + db.delete_column('dashboard_app_testrunfilter', 'build_number_attribute') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'dashboard_app.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}), + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'}) + }, + 'dashboard_app.bundle': { + 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'}, + '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}), + '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}), + 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}), + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}), + 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) + }, + 'dashboard_app.bundledeserializationerror': { + 'Meta': {'object_name': 'BundleDeserializationError'}, + 'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}), + 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'}) + }, + 'dashboard_app.bundlestream': { + 'Meta': {'object_name': 'BundleStream'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.hardwaredevice': { + 'Meta': {'object_name': 'HardwareDevice'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'dashboard_app.image': { + 'Meta': {'object_name': 'Image'}, + 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.imageattribute': { + 'Meta': {'object_name': 'ImageAttribute'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'required_attributes'", 'to': "orm['dashboard_app.Image']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + 'dashboard_app.imageset': { + 'Meta': {'object_name': 'ImageSet'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'}) + }, + 'dashboard_app.launchpadbug': { + 'Meta': {'object_name': 'LaunchpadBug'}, + 'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"}) + }, + 'dashboard_app.namedattribute': { + 'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.TextField', [], {}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'dashboard_app.softwarepackage': { + 'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'dashboard_app.softwarepackagescratch': { + 'Meta': {'object_name': 'SoftwarePackageScratch'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'dashboard_app.softwaresource': { + 'Meta': {'object_name': 'SoftwareSource'}, + 'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'dashboard_app.tag': { + 'Meta': {'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}) + }, + 'dashboard_app.test': { + 'Meta': {'object_name': 'Test'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}) + }, + 'dashboard_app.testcase': { + 'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}), + 'test_case_id': ('django.db.models.fields.TextField', [], {}), + 'units': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'dashboard_app.testingeffort': { + 'Meta': {'object_name': 'TestingEffort'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'testing_efforts'", 'to': "orm['lava_projects.Project']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'testing_efforts'", 'symmetrical': 'False', 'to': "orm['dashboard_app.Tag']"}) + }, + 'dashboard_app.testresult': { + 'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'}, + '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}), + 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.testrun': { + 'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'}, + 'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}), + 'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}), + 'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}), + 'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}), + 'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}), + 'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}), + 'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'dashboard_app.testrundenormalization': { + 'Meta': {'object_name': 'TestRunDenormalization'}, + 'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"}) + }, + 'dashboard_app.testrunfilter': { + 'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'}, + 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Test']", 'null': 'True', 'blank': 'True'}), + 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestCase']", 'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.testrunfilterattribute': { + 'Meta': {'object_name': 'TestRunFilterAttribute'}, + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + 'dashboard_app.testrunfiltersubscription': { + 'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'}, + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'lava_projects.project': { + 'Meta': {'object_name': 'Project'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}), + 'is_aggregate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}), + 'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['dashboard_app'] \ No newline at end of file === added file 'dashboard_app/migrations/0021_add_cast_integer.py' --- dashboard_app/migrations/0021_add_cast_integer.py 1970-01-01 00:00:00 +0000 +++ dashboard_app/migrations/0021_add_cast_integer.py 2012-09-06 03:12:26 +0000 @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models +import django.db.utils + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.start_transaction() + try: + db.execute("CREATE LANGUAGE plpgsql") + except django.db.utils.DatabaseError: + db.rollback_transaction() + db.start_transaction() + db.execute(""" +CREATE FUNCTION convert_to_integer(v_input text) +RETURNS INTEGER AS $a$ +DECLARE v_int_value INTEGER DEFAULT NULL; +BEGIN + BEGIN + v_int_value := v_input::INTEGER; + EXCEPTION WHEN OTHERS THEN + RETURN NULL; + END; +RETURN v_int_value; +END; +$a$ LANGUAGE plpgsql; + """) + db.commit_transaction() + + def backwards(self, orm): + db.execute("""DROP FUNCTION convert_to_integer (v_input text)""") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'dashboard_app.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}), + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'}) + }, + 'dashboard_app.bundle': { + 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'}, + '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}), + '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}), + 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}), + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}), + 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) + }, + 'dashboard_app.bundledeserializationerror': { + 'Meta': {'object_name': 'BundleDeserializationError'}, + 'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}), + 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'}) + }, + 'dashboard_app.bundlestream': { + 'Meta': {'object_name': 'BundleStream'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.hardwaredevice': { + 'Meta': {'object_name': 'HardwareDevice'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'dashboard_app.image': { + 'Meta': {'object_name': 'Image'}, + 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.imageattribute': { + 'Meta': {'object_name': 'ImageAttribute'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'required_attributes'", 'to': "orm['dashboard_app.Image']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + 'dashboard_app.imageset': { + 'Meta': {'object_name': 'ImageSet'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'}) + }, + 'dashboard_app.launchpadbug': { + 'Meta': {'object_name': 'LaunchpadBug'}, + 'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"}) + }, + 'dashboard_app.namedattribute': { + 'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.TextField', [], {}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'dashboard_app.softwarepackage': { + 'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'dashboard_app.softwarepackagescratch': { + 'Meta': {'object_name': 'SoftwarePackageScratch'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'dashboard_app.softwaresource': { + 'Meta': {'object_name': 'SoftwareSource'}, + 'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'dashboard_app.tag': { + 'Meta': {'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}) + }, + 'dashboard_app.test': { + 'Meta': {'object_name': 'Test'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}) + }, + 'dashboard_app.testcase': { + 'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}), + 'test_case_id': ('django.db.models.fields.TextField', [], {}), + 'units': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'dashboard_app.testingeffort': { + 'Meta': {'object_name': 'TestingEffort'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'testing_efforts'", 'to': "orm['lava_projects.Project']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'testing_efforts'", 'symmetrical': 'False', 'to': "orm['dashboard_app.Tag']"}) + }, + 'dashboard_app.testresult': { + 'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'}, + '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}), + 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.testrun': { + 'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'}, + 'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}), + 'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}), + 'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}), + 'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}), + 'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}), + 'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}), + 'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'dashboard_app.testrundenormalization': { + 'Meta': {'object_name': 'TestRunDenormalization'}, + 'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"}) + }, + 'dashboard_app.testrunfilter': { + 'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'}, + 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Test']", 'null': 'True', 'blank': 'True'}), + 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestCase']", 'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.testrunfilterattribute': { + 'Meta': {'object_name': 'TestRunFilterAttribute'}, + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + 'dashboard_app.testrunfiltersubscription': { + 'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'}, + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'lava_projects.project': { + 'Meta': {'object_name': 'Project'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}), + 'is_aggregate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}), + 'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['dashboard_app'] === modified file 'dashboard_app/models.py' --- dashboard_app/models.py 2012-08-31 01:24:49 +0000 +++ dashboard_app/models.py 2012-09-06 21:24:39 +0000 @@ -43,6 +43,7 @@ from django.db import models from django.db.models.fields import FieldDoesNotExist from django.db.models.signals import post_delete +from django.db.models.sql.aggregates import Aggregate as SQLAggregate from django.dispatch import receiver from django.template import Template, Context from django.template.defaultfilters import filesizeformat @@ -1505,7 +1506,7 @@ test_runs__test__test_id='lava', test_runs__attributes__name=self.build_number_attribute).extra( select={ - 'build_number': 'cast("dashboard_app_namedattribute"."value" as int)' + 'build_number': 'convert_to_integer("dashboard_app_namedattribute"."value")', }).extra( order_by=['-build_number'], )[:count] @@ -1571,32 +1572,46 @@ TestRunFilter.get_test_runs. """ - bundle = None - specific_results = None - result_count = None - pass_count = None - test_run = None filter = None + tag = None # either a date (bundle__uploaded_on) or a build number + test_runs = None + specific_results = None # Will stay none unless filter specifies a test case + pass_count = None # Only filled out for filters that dont specify a test + result_code = None # Ditto + + def _format_test_result(self, test_case, result): + if test_case.units: + if self.filter.test_case.units: + return '%s%s' % (result.measurement, result.units) + else: + return result.RESULT_MAP[result.result] + + def _format_test_run(self, test, tr): + return "%s %s pass / %s total" % ( + test.test_id, + tr.denormalization.count_pass, + tr.denormalization.count_all()) + + def _format_many_test_runs(self): + return "%s pass / %s total" % (self.pass_count, self.result_count) def format_for_mail(self): r = [' ~%s/%s ' % (self.filter.owner.username, self.filter.name)] if self.filter.test_case: - r.extend([ + r.append("%s:%s" % ( self.filter.test.test_id, - ':', self.filter.test_case.test_case_id, - ]) - for result in self.specific_results: - if self.filter.test_case.units: - result_desc = '%s%s' % (result.measurement, result.units) - else: - result_desc = result.RESULT_MAP[result.result] - r.extend([' ', result_desc]) + )) + r.append(' ' + ', '.join( + self._format_test_result(self.filter.test_case, r) + for r in self.specific_results)) elif self.filter.test: - r.append('%s %s pass/%s total' % ( - self.filter.test.test_id, self.pass_count, self.result_count)) + r.append(self.filter.test.test_id) + r.append(' ' + ', '.join( + self._format_test_run(self.filter.test, tr) + for tr in self.test_runs)) else: - r.append('%s pass/%s total' % (self.pass_count, self.result_count)) + r.append(self._format_many_test_runs()) r.append('\n') return ''.join(r) @@ -1605,22 +1620,81 @@ """Wrap a QuerySet and construct FilterMatchs from what the wrapped query set returns. - Just enough of the QuerySet API to work with DataTable.""" + Just enough of the QuerySet API to work with DataTable (i.e. ordering and + slicing).""" model = TestRun - def __init__(self, queryset, filter): + def __init__(self, queryset, filter_data): self.queryset = queryset - self.filter = filter + self.filter_data = filter_data + if filter_data['build_number_attribute']: + self.key = 'build_number' + self.key_name = 'Build' + else: + self.key = 'bundle__uploaded_on' + self.key_name = 'Uploaded On' + if filter_data['test_case']: + self.has_specific_results = True + else: + self.has_specific_results = False def _makeMatches(self, data): - raise NotImplementedError(self._makeMatches) + test_run_ids = set() + for datum in data: + test_run_ids.update(datum['id__arrayagg']) + r = [] + trs = TestRun.objects.filter(id__in=test_run_ids).select_related( + 'denormalization', 'bundle', 'bundle__bundle_stream') + trs_by_id = {} + for tr in trs: + trs_by_id[tr.id] = tr + if self.has_specific_results: + result_ids_by_tr_id = {} + results_by_tr_id = {} + values = TestRun.objects.filter( + id__in=test_run_ids, + test_results__test_case=self.filter_data['test_case']).values_list( + 'id', 'test_results') + result_ids = set() + for v in values: + result_ids_by_tr_id.setdefault(v[0], []).append(v[1]) + result_ids.add(v[1]) + + results_by_id = {} + for result in TestResult.objects.filter( + id__in=list(result_ids)).select_related( + 'test', 'test_case', 'test_run__bundle__bundle_stream'): + results_by_id[result.id] = result + + for tr_id, result_ids in result_ids_by_tr_id.items(): + rs = results_by_tr_id[tr_id] = [] + for result_id in result_ids: + rs.append(results_by_id[result_id]) + for datum in data: + trs = [] + for id in datum['id__arrayagg']: + trs.append(trs_by_id[id]) + match = FilterMatch() + match.test_runs = trs + match.filter_data = self.filter_data + match.tag = datum[self.key] + if self.has_specific_results: + match.specific_results = [] + for id in datum['id__arrayagg']: + match.specific_results.extend(results_by_tr_id[id]) + else: + match.pass_count = sum(tr.denormalization.count_pass for tr in trs) + match.result_count = sum(tr.denormalization.count_all() for tr in trs) + r.append(match) + return iter(r) def _wrap(self, queryset, **kw): - return self.__class__(queryset, self.filter, **kw) + return self.__class__(queryset, self.filter_data, **kw) def order_by(self, *args): - return self._wrap(self.queryset.order_by(*args)) + # the generic tables code calls this even when it shouldn't... + return self def count(self): return self.queryset.count() @@ -1633,102 +1707,6 @@ return self._makeMatches(data) -class SpecificTestCaseMatchMakingQuerySet(MatchMakingQuerySet): - - def _makeMatches(self, runs): - results_by_run_id = {} - for run in runs: - results_by_run_id[run.id] = [] - results = TestResult.objects.filter( - test_run_id__in=results_by_run_id.keys(), - test_case_id=self.filter.test_case.id) - for result in results: - results_by_run_id[result.test_run_id].append(result) - matches = [] - for run in runs: - match = FilterMatch() - specific_results = results_by_run_id[result.test_run_id] - match.specific_results = specific_results - match.result_count = len(specific_results) - match.pass_count = len([r for r in specific_results if r.result == r.RESULT_PASS]) - match.test_run = run - match.bundle = run.bundle - match.filter = self.filter - matches.append(match) - return iter(matches) - - - -class SpecificTestMatchMakingQuerySet(MatchMakingQuerySet): - def _makeMatches(self, runs): - matches = [] - for run in runs: - match = FilterMatch() - match.specific_results = None - match.result_count = run.denormalization.count_all() - match.pass_count = run.denormalization.count_pass - match.test_run = run - match.bundle = run.bundle - match.filter = self.filter - matches.append(match) - return iter(matches) - - -class BundleMatchMakingQuerySet(MatchMakingQuerySet): - - model = Bundle - - def __init__(self, queryset, filter, mis_ordered=False): - super(BundleMatchMakingQuerySet, self).__init__(queryset, filter) - self.mis_ordered = mis_ordered - - def _makeMatches(self, bundles): - assert not self.mis_ordered, """ - attempt to materialize BundleMatchMakingQuerySet when ordered on - non-bundle field""" - matches = [] - counted_bundles = Bundle.objects.filter( - id__in=[b.id for b in bundles]).annotate( - pass_count=models.Sum('test_runs__denormalization__count_pass'), - unknown_count=models.Sum('test_runs__denormalization__count_unknown'), - skip_count=models.Sum('test_runs__denormalization__count_skip'), - fail_count=models.Sum('test_runs__denormalization__count_fail')) - bundles_by_id = {} - for bundle in counted_bundles: - bundles_by_id[bundle.id] = bundle - for bundle in bundles: - match = FilterMatch() - match.specific_results = None - cb = bundles_by_id[bundle.id] - match.result_count = cb.unknown_count + cb.skip_count + cb.pass_count + cb.fail_count - match.pass_count = cb.pass_count - match.test_run = None - match.bundle = bundle - match.filter = self.filter - matches.append(match) - return iter(matches) - - def _wrap(self, queryset, **kw): - if 'mis_ordered' not in kw: - kw['mis_ordered'] = self.mis_ordered - return self.__class__(queryset, self.filter, **kw) - - def order_by(self, field): - if field.startswith('bundle__') or field.startswith('-bundle__'): - if field.startswith('-'): - prefix = '-' - field = field[1:] - else: - prefix = '' - field = field[len('bundle__'):] - r = super(BundleMatchMakingQuerySet, self).order_by( - prefix+field) - r.mis_ordered = False - return r - else: - return self._wrap(self.queryset, mis_ordered=True) - - class TestRunFilterAttribute(models.Model): name = models.CharField(max_length=1024) @@ -1740,6 +1718,22 @@ return '%s = %s' % (self.name, self.value) +class SQLArrayAgg(SQLAggregate): + sql_function = 'array_agg' + + +class ArrayAgg(models.Aggregate): + name = 'ArrayAgg' + def add_to_query(self, query, alias, col, source, is_summary): + aggregate = SQLArrayAgg( + col, source=source, is_summary=is_summary, **self.extra) + # For way more detail than you want about what this next line is for, + # see + # http://voices.canonical.com/michael.hudson/2012/09/02/using-postgres-array_agg-from-django/ + aggregate.field = models.DecimalField() # vomit + query.aggregates[alias] = aggregate + + class TestRunFilter(models.Model): owner = models.ForeignKey(User) @@ -1765,6 +1759,10 @@ public = models.BooleanField( default=False, help_text="Whether other users can see this filter.") + build_number_attribute = models.CharField( + max_length=1024, blank=True, null=True, + help_text="For some filters, there is a natural build number. If you specify the name of the attribute that contains the build number here, the results of the filter will be grouped and ordered by this build number.") + @property def summary_data(self): return { @@ -1772,6 +1770,7 @@ 'attributes': self.attributes.all().values_list('name', 'value'), 'test': self.test, 'test_case': self.test_case, + 'build_number_attribute': self.build_number_attribute, } def __unicode__(self): @@ -1803,36 +1802,50 @@ def get_test_runs_impl(self, user, bundle_streams, attributes): accessible_bundle_streams = BundleStream.objects.accessible_by_principal( user) - testruns = TestRun.objects.filter( - models.Q(bundle__bundle_stream__in=accessible_bundle_streams), - models.Q(bundle__bundle_stream__in=bundle_streams), - ) + bs_ids = [bs.id for bs in set(accessible_bundle_streams) & set(bundle_streams)] + conditions = [models.Q(bundle__bundle_stream__id__in=bs_ids)] + + content_type_id = ContentType.objects.get_for_model(TestRun).id for (name, value) in attributes: - testruns = TestRun.objects.filter( - id__in=testruns.values_list('id'), - attributes__name=name, attributes__value=value) + # We punch through the generic relation abstraction here for 100x + # better performance. + conditions.append( + models.Q(id__in=NamedAttribute.objects.filter( + name=name, value=value, content_type_id=content_type_id + ).values('object_id'))) if self.test_case: - testruns = TestRun.objects.filter( - id__in=testruns.values_list('id'), + conditions.append(models.Q( test_results__test_case=self.test_case, - test=self.test_case.test) - wrapper_cls = SpecificTestCaseMatchMakingQuerySet + test=self.test_case.test)) elif self.test: - testruns = TestRun.objects.filter( - id__in=testruns.values_list('id'), - test=self.test) - wrapper_cls = SpecificTestMatchMakingQuerySet + conditions.append(models.Q(test=self.test)) + + testruns = TestRun.objects.filter(*conditions) + + if self.build_number_attribute: + testruns = testruns.filter( + attributes__name=self.build_number_attribute).extra( + select={ + 'build_number': 'convert_to_integer("dashboard_app_namedattribute"."value")', + }, + where=['convert_to_integer("dashboard_app_namedattribute"."value") IS NOT NULL']).extra( + order_by=['-build_number'], + ).values('build_number').annotate(ArrayAgg('id')) else: - # if the filter doesn't specify a test, we still only return one - # test run per bundle. the display code knows to do different - # things in this case. - testruns = Bundle.objects.filter( - test_runs__in=testruns) - wrapper_cls = BundleMatchMakingQuerySet - - return wrapper_cls(testruns, self) + testruns = testruns.order_by('-bundle__uploaded_on').values( + 'bundle__uploaded_on').annotate(ArrayAgg('id')) + + filter_data = { + 'bundle_streams': bundle_streams, + 'attributes': attributes, + 'test': self.test, + 'test_case': self.test_case, + 'build_number_attribute': self.build_number_attribute, + } + + return MatchMakingQuerySet(testruns, filter_data) # given bundle: # select from filter @@ -1864,42 +1877,33 @@ select django_content_type.id from django_content_type where app_label = 'dashboard_app' and model='testrun') and object_id = dashboard_app_testrun.id))) - from dashboard_app_testrun where dashboard_app_testrun.bundle_id = %s) = 0 """ % bundle.id], + from dashboard_app_testrun where dashboard_app_testrun.bundle_id = %s) = 0""" % bundle.id], ) filters = list(filters) matches = [] + bundle_with_counts = Bundle.objects.annotate( + pass_count=models.Sum('test_runs__denormalization__count_pass'), + unknown_count=models.Sum('test_runs__denormalization__count_unknown'), + skip_count=models.Sum('test_runs__denormalization__count_skip'), + fail_count=models.Sum('test_runs__denormalization__count_fail')).get( + id=bundle.id) for filter in filters: if filter.test: - for test_run in bundle.test_runs.filter(test=filter.test): - match = FilterMatch() - match.filter = filter - match.test_run = test_run - if filter.test_case: - match.specific_results = list( - test_run.test_results.filter(test_case=filter.test_case)) - match.result_count = len(match.specific_results) - match.pass_count = len( - [r for r in match.specific_results if r.result == r.RESULT_PASS]) - else: - match.specific_results = None - match.result_count = test_run.denormalization.count_all() - match.pass_count = test_run.denormalization.count_pass - matches.append(match) + match = FilterMatch() + match.test_runs = list(bundle.test_runs.filter(test=filter.test)) + match.filter = filter + if filter.test_case: + match.specific_results = list( + TestResult.objects.filter(test_case=filter.test_case, test_run__bundle=bundle)) + matches.append(match) else: match = FilterMatch() match.filter = filter - match.test_run = None - bundle_with_counts = Bundle.objects.annotate( - pass_count=models.Sum('test_runs__denormalization__count_pass'), - unknown_count=models.Sum('test_runs__denormalization__count_unknown'), - skip_count=models.Sum('test_runs__denormalization__count_skip'), - fail_count=models.Sum('test_runs__denormalization__count_fail')).get( - id=bundle.id) - match.specific_results = None + match.test_runs = list(bundle.test_runs.all()) b = bundle_with_counts match.result_count = b.unknown_count + b.skip_count + b.pass_count + b.fail_count match.pass_count = bundle_with_counts.pass_count - matches.append(match) + matches.append(match) return matches def get_test_runs(self, user): === modified file 'dashboard_app/templates/dashboard_app/filter_form.html' --- dashboard_app/templates/dashboard_app/filter_form.html 2012-08-16 00:14:22 +0000 +++ dashboard_app/templates/dashboard_app/filter_form.html 2012-09-04 02:13:52 +0000 @@ -23,6 +23,14 @@
{{ form.bundle_streams.help_text|safe }}
+ Build Number: +
+
+ {{ form.build_number_attribute.errors }} + {{ form.build_number_attribute.label_tag }}: {{ form.build_number_attribute }} +
{{ form.build_number_attribute.help_text|safe }} +
+
Attributes:
=== modified file 'dashboard_app/templates/dashboard_app/filter_summary.html' --- dashboard_app/templates/dashboard_app/filter_summary.html 2012-08-17 03:51:42 +0000 +++ dashboard_app/templates/dashboard_app/filter_summary.html 2012-09-05 02:59:43 +0000 @@ -21,6 +21,16 @@ {% endif %} +{% if summary_data.build_number_attribute %} + + + Build Number Attribute + + + {{ summary_data.build_number_attribute }} + + +{% endif %} Test case === modified file 'dashboard_app/views.py' --- dashboard_app/views.py 2012-09-06 22:01:28 +0000 +++ dashboard_app/views.py 2012-09-09 23:05:58 +0000 @@ -20,6 +20,7 @@ Views for the Dashboard application """ +import operator import re import json @@ -457,6 +458,12 @@ {% endfor %} ''') + build_number_attribute = Column() + def render_build_number_attribute(self, value): + if not value: + return '' + return value + attributes = TemplateColumn(''' {% for a in record.attributes.all %} {{ a }}
@@ -543,53 +550,85 @@ class FilterTable(DataTablesTable): def __init__(self, *args, **kwargs): - filter = kwargs['params'][1] - data = filter.summary_data super(FilterTable, self).__init__(*args, **kwargs) - if len(data['bundle_streams']) == 1: - del self.base_columns['bundle_stream'] - if data['test_case']: - del self.base_columns['bundle'] - del self.base_columns['passes'] - del self.base_columns['total'] - self.base_columns['specific_results'].verbose_name = mark_safe( - data['test_case'].test_case_id) - elif data['test']: - del self.base_columns['bundle'] - del self.base_columns['specific_results'] + match_maker = self.data.queryset + self.base_columns['tag'].verbose_name = match_maker.key_name + bundle_stream_col = self.base_columns.pop('bundle_stream') + bundle_col = self.base_columns.pop('bundle') + tag_col = self.base_columns.pop('tag') + test_run_col = self.base_columns.pop('test_run') + specific_results_col = self.base_columns.pop('specific_results') + if match_maker.filter_data['test_case']: + del self.base_columns['passes'] + del self.base_columns['total'] + col_name = '%s:%s' % ( + match_maker.filter_data['test'].test_id, + match_maker.filter_data['test_case'].test_case_id + ) + specific_results_col.verbose_name = mark_safe(col_name) + self.base_columns.insert(0, 'specific_results', specific_results_col) + elif match_maker.filter_data['test']: + del self.base_columns['passes'] + del self.base_columns['total'] + test_run_col.verbose_name = mark_safe(match_maker.filter_data['test'].test_id) + self.base_columns.insert(0, 'test_run', test_run_col) else: - del self.base_columns['test_run'] - self.base_columns['passes'] - self.base_columns['total'] - del self.base_columns['specific_results'] - uploaded_col_index = self.base_columns.keys().index('uploaded_on') - self.datatable_opts = self.datatable_opts.copy() - self.datatable_opts['aaSorting'] = [[uploaded_col_index, 'desc']] - self._compute_queryset(kwargs['params']) - - bundle_stream = Column(accessor='bundle.bundle_stream') - - bundle = BundleColumn(accessor='bundle', sortable=False) - - test_run = TemplateColumn( - '' - '{{ record.test_run.test }} results', - accessor="test__test_id", - ) - - uploaded_on = TemplateColumn( - '{{ record.bundle.uploaded_on|date:"Y-m-d H:i:s" }}', - accessor='bundle__uploaded_on') - - passes = Column(accessor='pass_count', sortable=False) - total = Column(accessor='result_count', sortable=False) - specific_results = SpecificCaseColumn(accessor='specific_results', sortable=False) + self.base_columns.insert(0, 'bundle', bundle_col) + if len(match_maker.filter_data['bundle_streams']) > 1: + self.base_columns.insert(0, 'bundle_stream', bundle_stream_col) + self.base_columns.insert(0, 'tag', tag_col) + + tag = Column() + + def render_bundle_stream(self, record): + bundle_streams = set(tr.bundle.bundle_stream for tr in record.test_runs) + links = [] + for bs in sorted(bundle_streams, key=operator.attrgetter('pathname')): + links.append('%s' % ( + bs.get_absolute_url(), escape(bs.pathname))) + return mark_safe('
'.join(links)) + bundle_stream = Column(mark_safe("Bundle Stream(s)")) + + def render_bundle(self, record): + bundles = set(tr.bundle for tr in record.test_runs) + links = [] + for b in sorted(bundles, key=operator.attrgetter('uploaded_on')): + links.append('%s' % ( + b.get_absolute_url(), escape(b.content_filename))) + 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) datatable_opts = { "sPaginationType": "full_numbers", "iDisplayLength": 25, + "bSort": False, }