From patchwork Mon Sep 3 00:17:10 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Michael-Doyle Hudson X-Patchwork-Id: 11141 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 6F3DD23F27 for ; Mon, 3 Sep 2012 00:17:15 +0000 (UTC) Received: from mail-iy0-f180.google.com (mail-iy0-f180.google.com [209.85.210.180]) by fiordland.canonical.com (Postfix) with ESMTP id 0D3EFA18501 for ; Mon, 3 Sep 2012 00:16:28 +0000 (UTC) Received: by iafj25 with SMTP id j25so6790410iaf.11 for ; Sun, 02 Sep 2012 17:17: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=CXuO0iHSfgJJXSuJWweZzzuZl3kZtsNQc1MtEtgjkEw=; b=I0VuLJ/I9LKWsh+rH53wCa3gIW0ejK4Te8Yw8SYcgD+vKweg+cS6YMXrJSYwkBNXRe dSZX2sQm/Ls2UjjWB23asugX3ueep+jkg4erVBuAlen8911C8wnCAVvUZNf5TnMMLq7M suDQGjaKm/hitCPM+T6sL3q9+jko6+1QM5VsXRXWblrmkH8Gt4GulUbGX5WfeRrDd6Pn yvRm/oc90m1qw8DwxYXF+XA8DSY66qwCy4KnCXcZYKisBZc5Fb2twlx7YcyHvfofCBiU I+ogx0JWsdOyyMSwVLoMDKdQt5mcaVctrl0YDBQU3gqxDm1fK8XomGdQZufq3P87f8yz 7iiA== Received: by 10.50.159.196 with SMTP id xe4mr9088007igb.43.1346631434033; Sun, 02 Sep 2012 17:17: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 ex8csp109905igc; Sun, 2 Sep 2012 17:17:12 -0700 (PDT) Received: by 10.180.104.197 with SMTP id gg5mr19321869wib.9.1346631432018; Sun, 02 Sep 2012 17:17:12 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id j46si15108786wea.8.2012.09.02.17.17.10 (version=TLSv1/SSLv3 cipher=OTHER); Sun, 02 Sep 2012 17:17:12 -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 1T8KLm-0006hX-P5 for ; Mon, 03 Sep 2012 00:17:10 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id AAA58E01C5 for ; Mon, 3 Sep 2012 00:17:10 +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: 337 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-dashboard/trunk] Rev 337: add the (beta) concept of a test run filter and subscription Message-Id: <20120903001710.23575.15870.launchpad@ackee.canonical.com> Date: Mon, 03 Sep 2012 00:17:10 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="15890"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 4be91b7565429d10eba5256f287f3253249061d6 X-Gm-Message-State: ALoCoQmPLzacsBArSecwAQe5X9csfa7wSrQPYOiQ7HGcTkisfsECn+8Wlja7f7gpzP+CRPLRc5dI Merge authors: Michael Hudson-Doyle (mwhudson) Spring Zhang (qzhang) Related merge proposals: https://code.launchpad.net/~mwhudson/lava-dashboard/test-run-filter-subscription/+merge/121105 proposed by: Michael Hudson-Doyle (mwhudson) review: Approve - Andy Doan (doanac) https://code.launchpad.net/~mwhudson/lava-dashboard/test-run-filter-public/+merge/120271 proposed by: Michael Hudson-Doyle (mwhudson) https://code.launchpad.net/~qzhang/lava-dashboard/user-notification/+merge/113167 proposed by: Spring Zhang (qzhang) review: Needs Fixing - Michael Hudson-Doyle (mwhudson) review: Resubmit - Spring Zhang (qzhang) ------------------------------------------------------------ revno: 337 [merge] committer: Michael Hudson-Doyle branch nick: trunk timestamp: Mon 2012-09-03 12:16:21 +1200 message: add the (beta) concept of a test run filter and subscription added: dashboard_app/migrations/0017_auto__add_testrunfilterattribute__add_testrunfilter__add_unique_testru.py dashboard_app/migrations/0018_auto__add_field_testrunfilter_public.py dashboard_app/migrations/0019_auto__add_testrunfiltersubscription__add_unique_testrunfiltersubscript.py dashboard_app/static/css/filter-edit.css dashboard_app/static/js/filter-edit.js dashboard_app/templates/dashboard_app/filter_add.html dashboard_app/templates/dashboard_app/filter_delete.html dashboard_app/templates/dashboard_app/filter_detail.html dashboard_app/templates/dashboard_app/filter_form.html dashboard_app/templates/dashboard_app/filter_preview.html dashboard_app/templates/dashboard_app/filter_subscribe.html dashboard_app/templates/dashboard_app/filter_subscription_mail.txt dashboard_app/templates/dashboard_app/filter_summary.html dashboard_app/templates/dashboard_app/filters_list.html modified: dashboard_app/admin.py dashboard_app/extension.py dashboard_app/models.py dashboard_app/urls.py dashboard_app/views.py doc/changes.rst --- 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-07-18 21:43:42 +0000 +++ dashboard_app/admin.py 2012-08-17 02:05:39 +0000 @@ -44,6 +44,9 @@ TestCase, TestResult, TestRun, + TestRunFilter, + TestRunFilterAttribute, + TestRunFilterSubscription, TestingEffort, ) @@ -196,9 +199,20 @@ filter_horizontal = ['images'] save_as = True + class LaunchpadBugAdmin(admin.ModelAdmin): raw_id_fields = ['test_runs'] + +class TestRunFilterAdmin(admin.ModelAdmin): + filter_horizontal = ['bundle_streams'] + class TestRunFilterAttributeInline(admin.TabularInline): + model = TestRunFilterAttribute + inlines = [TestRunFilterAttributeInline] + raw_id_fields = ['test_case'] + save_as = True + + admin.site.register(Attachment) admin.site.register(Bundle, BundleAdmin) admin.site.register(BundleDeserializationError, BundleDeserializationErrorAdmin) @@ -213,5 +227,7 @@ admin.site.register(TestCase, TestCaseAdmin) admin.site.register(TestResult, TestResultAdmin) admin.site.register(TestRun, TestRunAdmin) +admin.site.register(TestRunFilter, TestRunFilterAdmin) +admin.site.register(TestRunFilterSubscription) admin.site.register(Tag) admin.site.register(TestingEffort, TestingEffortAdmin) === modified file 'dashboard_app/extension.py' --- dashboard_app/extension.py 2012-07-13 01:22:03 +0000 +++ dashboard_app/extension.py 2012-09-03 00:09:09 +0000 @@ -46,6 +46,7 @@ Menu("Data Views", reverse("dashboard_app.views.data_view_list")), Menu("Reports", reverse("dashboard_app.views.report_list")), Menu("Image Reports", reverse("dashboard_app.views.image_report_list")), + Menu("[BETA] Filters", reverse("dashboard_app.views.filters_list")), ] return menu === added file 'dashboard_app/migrations/0017_auto__add_testrunfilterattribute__add_testrunfilter__add_unique_testru.py' --- dashboard_app/migrations/0017_auto__add_testrunfilterattribute__add_testrunfilter__add_unique_testru.py 1970-01-01 00:00:00 +0000 +++ dashboard_app/migrations/0017_auto__add_testrunfilterattribute__add_testrunfilter__add_unique_testru.py 2012-08-13 01:30:49 +0000 @@ -0,0 +1,290 @@ +# -*- 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 'TestRunFilterAttribute' + db.create_table('dashboard_app_testrunfilterattribute', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=1024)), + ('value', self.gf('django.db.models.fields.CharField')(max_length=1024)), + ('filter', self.gf('django.db.models.fields.related.ForeignKey')(related_name='attributes', to=orm['dashboard_app.TestRunFilter'])), + )) + db.send_create_signal('dashboard_app', ['TestRunFilterAttribute']) + + # Adding model 'TestRunFilter' + db.create_table('dashboard_app_testrunfilter', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('name', self.gf('django.db.models.fields.SlugField')(max_length=1024)), + ('test', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.Test'], null=True, blank=True)), + ('test_case', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.TestCase'], null=True, blank=True)), + )) + db.send_create_signal('dashboard_app', ['TestRunFilter']) + + # Adding M2M table for field bundle_streams on 'TestRunFilter' + db.create_table('dashboard_app_testrunfilter_bundle_streams', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('testrunfilter', models.ForeignKey(orm['dashboard_app.testrunfilter'], null=False)), + ('bundlestream', models.ForeignKey(orm['dashboard_app.bundlestream'], null=False)) + )) + db.create_unique('dashboard_app_testrunfilter_bundle_streams', ['testrunfilter_id', 'bundlestream_id']) + + # Adding unique constraint on 'TestRunFilter', fields ['owner', 'name'] + db.create_unique('dashboard_app_testrunfilter', ['owner_id', 'name']) + + + def backwards(self, orm): + # Removing unique constraint on 'TestRunFilter', fields ['owner', 'name'] + db.delete_unique('dashboard_app_testrunfilter', ['owner_id', 'name']) + + # Deleting model 'TestRunFilterAttribute' + db.delete_table('dashboard_app_testrunfilterattribute') + + # Deleting model 'TestRunFilter' + db.delete_table('dashboard_app_testrunfilter') + + # Removing M2M table for field bundle_streams on 'TestRunFilter' + db.delete_table('dashboard_app_testrunfilter_bundle_streams') + + + 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'}, + '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']"}), + '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'}) + }, + '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/0018_auto__add_field_testrunfilter_public.py' --- dashboard_app/migrations/0018_auto__add_field_testrunfilter_public.py 1970-01-01 00:00:00 +0000 +++ dashboard_app/migrations/0018_auto__add_field_testrunfilter_public.py 2012-08-15 23:40:53 +0000 @@ -0,0 +1,257 @@ +# -*- 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.public' + db.add_column('dashboard_app_testrunfilter', 'public', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'TestRunFilter.public' + db.delete_column('dashboard_app_testrunfilter', 'public') + + + 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'}, + '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'}) + }, + '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/0019_auto__add_testrunfiltersubscription__add_unique_testrunfiltersubscript.py' --- dashboard_app/migrations/0019_auto__add_testrunfiltersubscription__add_unique_testrunfiltersubscript.py 1970-01-01 00:00:00 +0000 +++ dashboard_app/migrations/0019_auto__add_testrunfiltersubscription__add_unique_testrunfiltersubscript.py 2012-08-16 00:44:01 +0000 @@ -0,0 +1,274 @@ +# -*- 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 'TestRunFilterSubscription' + db.create_table('dashboard_app_testrunfiltersubscription', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('filter', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.TestRunFilter'])), + ('level', self.gf('django.db.models.fields.IntegerField')(default=0)), + )) + db.send_create_signal('dashboard_app', ['TestRunFilterSubscription']) + + # Adding unique constraint on 'TestRunFilterSubscription', fields ['user', 'filter'] + db.create_unique('dashboard_app_testrunfiltersubscription', ['user_id', 'filter_id']) + + + def backwards(self, orm): + # Removing unique constraint on 'TestRunFilterSubscription', fields ['user', 'filter'] + db.delete_unique('dashboard_app_testrunfiltersubscription', ['user_id', 'filter_id']) + + # Deleting model 'TestRunFilterSubscription' + db.delete_table('dashboard_app_testrunfiltersubscription') + + + 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'}, + '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 === modified file 'dashboard_app/models.py' --- dashboard_app/models.py 2012-08-27 00:31:59 +0000 +++ dashboard_app/models.py 2012-08-31 01:24:49 +0000 @@ -34,9 +34,11 @@ from django.contrib.auth.models import User from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ValidationError +from django.contrib.sites.models import Site +from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.files import locks, File from django.core.files.storage import FileSystemStorage +from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.db import models from django.db.models.fields import FieldDoesNotExist @@ -44,6 +46,7 @@ from django.dispatch import receiver from django.template import Template, Context from django.template.defaultfilters import filesizeformat +from django.template.loader import render_to_string from django.utils.translation import ugettext as _ from django.utils.translation import ungettext @@ -491,7 +494,6 @@ return try: self._do_deserialize(prefer_evolution) - bundle_was_deserialized.send(sender=self, bundle=self) except Exception as ex: import_error = BundleDeserializationError.objects.get_or_create( bundle=self)[0] @@ -505,6 +507,7 @@ pass self.is_deserialized = True self.save() + bundle_was_deserialized.send_robust(sender=self, bundle=self) def _do_deserialize(self, prefer_evolution): """ @@ -1210,7 +1213,7 @@ """ repository = DataViewRepository() - + def __init__(self, name, backend_queries, arguments, documentation, summary): self.name = name self.backend_queries = backend_queries @@ -1308,7 +1311,7 @@ Data reports are small snippets of xml that define a limited django template. """ - + repository = DataReportRepository() def __init__(self, **kwargs): @@ -1544,7 +1547,7 @@ for field_name in meta.get_all_field_names(): # object that represents the metadata of the field - try: + try: field_meta = meta.get_field(field_name) except FieldDoesNotExist: continue @@ -1559,3 +1562,422 @@ # the 'path' attribute contains the name of the file we need if hasattr(field, 'path') and os.path.exists(field.path): field.storage.delete(field.path) + + +class FilterMatch(object): + """A non-database object that represents the way a filter matches a test_run. + + Returned by TestRunFilter.matches_against_bundle and + TestRunFilter.get_test_runs. + """ + + bundle = None + specific_results = None + result_count = None + pass_count = None + test_run = None + filter = None + + def format_for_mail(self): + r = [' ~%s/%s ' % (self.filter.owner.username, self.filter.name)] + if self.filter.test_case: + r.extend([ + 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]) + elif self.filter.test: + r.append('%s %s pass/%s total' % ( + self.filter.test.test_id, self.pass_count, self.result_count)) + else: + r.append('%s pass/%s total' % (self.pass_count, self.result_count)) + r.append('\n') + return ''.join(r) + + +class MatchMakingQuerySet(object): + """Wrap a QuerySet and construct FilterMatchs from what the wrapped query + set returns. + + Just enough of the QuerySet API to work with DataTable.""" + + model = TestRun + + def __init__(self, queryset, filter): + self.queryset = queryset + self.filter = filter + + def _makeMatches(self, data): + raise NotImplementedError(self._makeMatches) + + def _wrap(self, queryset, **kw): + return self.__class__(queryset, self.filter, **kw) + + def order_by(self, *args): + return self._wrap(self.queryset.order_by(*args)) + + def count(self): + return self.queryset.count() + + def __getitem__(self, item): + return self._wrap(self.queryset[item]) + + def __iter__(self): + data = list(self.queryset) + 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) + value = models.CharField(max_length=1024) + + filter = models.ForeignKey("TestRunFilter", related_name="attributes") + + def __unicode__(self): + return '%s = %s' % (self.name, self.value) + + +class TestRunFilter(models.Model): + + owner = models.ForeignKey(User) + + name = models.SlugField( + max_length=1024, + help_text=("The name of a filter is used to refer to it in " + "the web UI and in email notifications triggered by this " + "filter.")) + class Meta: + unique_together = (('owner', 'name')) + + 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.") + + @property + def summary_data(self): + return { + 'bundle_streams': self.bundle_streams.all(), + 'attributes': self.attributes.all().values_list('name', 'value'), + 'test': self.test, + 'test_case': self.test_case, + } + + 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) + + + # given filter: + # select from testrun + # where testrun.bundle in filter.bundle_streams ^ accessible_bundles + # and testrun has attribute with key = key1 and value = value1 + # 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 + + 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), + ) + + for (name, value) in attributes: + testruns = TestRun.objects.filter( + id__in=testruns.values_list('id'), + attributes__name=name, attributes__value=value) + + if self.test_case: + testruns = TestRun.objects.filter( + id__in=testruns.values_list('id'), + test_results__test_case=self.test_case, + test=self.test_case.test) + wrapper_cls = SpecificTestCaseMatchMakingQuerySet + elif self.test: + testruns = TestRun.objects.filter( + id__in=testruns.values_list('id'), + test=self.test) + wrapper_cls = SpecificTestMatchMakingQuerySet + 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) + + # given bundle: + # select from filter + # where bundle.bundle_stream in filter.bundle_streams + # and filter.test in (select test from bundle.test_runs) + # and all the attributes on the filter are on a testrun in the bundle + # = the minimum over testrun (the number of attributes on the filter that are not on the testrun) is 0 + # and (filter.test_case is null + # or filter.test_case in select test_case from bundle.test_runs.test_results.test_cases) + + @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( + where=[ + """(select min((select count(*) + from dashboard_app_testrunfilterattribute + where filter_id = dashboard_app_testrunfilter.id + and (name, value) not in (select name, value + from dashboard_app_namedattribute + where content_type_id = ( + 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], + ) + filters = list(filters) + matches = [] + 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) + 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 + 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')) + + @models.permalink + def get_absolute_url(self): + return ( + "dashboard_app.views.filter_detail", + [self.owner.username, self.name]) + + +class TestRunFilterSubscription(models.Model): + + user = models.ForeignKey(User) + + filter = models.ForeignKey(TestRunFilter) + + class Meta: + unique_together = (('user', 'filter')) + + NOTIFICATION_FAILURE, NOTIFICATION_ALWAYS = range(2) + + NOTIFICATION_CHOICES = ( + (NOTIFICATION_FAILURE, "Only when failed"), + (NOTIFICATION_ALWAYS, "Always")) + + level = models.IntegerField( + default=NOTIFICATION_FAILURE, choices=NOTIFICATION_CHOICES, + help_text=("You can choose to be notified by email:
  • whenever a test " + "that matches the criteria of this filter is executed" + "
  • only when a test that matches the criteria of this filter fails
")) + + @classmethod + def recipients_for_bundle(cls, bundle): + matches = TestRunFilter.matches_against_bundle(bundle) + matches_by_filter_id = {} + for match in matches: + matches_by_filter_id[match.filter.id] = match + args = [models.Q(filter_id__in=list(matches_by_filter_id))] + bs = bundle.bundle_stream + if not bs.is_public: + if bs.group: + args.append(models.Q(user__in=bs.group.user_set.all())) + else: + args.append(models.Q(user=bs.user)) + subscriptions = TestRunFilterSubscription.objects.filter(*args) + 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 + recipients.setdefault(sub.user, []).append(match) + return recipients + + +def send_bundle_notifications(sender, bundle, **kwargs): + recipients = TestRunFilterSubscription.recipients_for_bundle(bundle) + domain = '???' + try: + site = Site.objects.get_current() + except (Site.DoesNotExist, ImproperlyConfigured): + pass + else: + domain = site.domain + url_prefix = 'http://%s' % domain + for user, matches in recipients.items(): + data = {'bundle': bundle, 'user': user, 'matches': matches, 'url_prefix': url_prefix} + mail = render_to_string( + 'dashboard_app/filter_subscription_mail.txt', + data) + filter_names = ', '.join(match.filter.name for match in matches) + send_mail( + "LAVA result notification: " + filter_names, mail, + settings.SERVER_EMAIL, [user.email]) + + +bundle_was_deserialized.connect(send_bundle_notifications) === added file 'dashboard_app/static/css/filter-edit.css' --- dashboard_app/static/css/filter-edit.css 1970-01-01 00:00:00 +0000 +++ dashboard_app/static/css/filter-edit.css 2012-08-14 02:25:44 +0000 @@ -0,0 +1,10 @@ +@import url("../../admin/css/widgets.css"); +div.selector span.helptext { display: none; } +div.selector h2 { margin: 0; font-size: 11pt; } +div.selector a { text-decoration: none; } +div.selector select { height: 10em; } +div.selector ul.selector-chooser { margin-top: 5.5em; } +div.selector .selector-chosen select { + border: 1px solid rgb(204, 204, 204); + border-top: none; +} === added file 'dashboard_app/static/js/filter-edit.js' --- dashboard_app/static/js/filter-edit.js 1970-01-01 00:00:00 +0000 +++ dashboard_app/static/js/filter-edit.js 2012-08-20 23:55:03 +0000 @@ -0,0 +1,67 @@ +var row_number; +$(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'); + } +}; +$("#id_test").change(updateTestCasesFromTest); +row_number = $("#attribute-table tbody tr").size(); +$("#add-attribute").click( + function (e) { + e.preventDefault(); + var body = $("#attribute-table tbody"); + var row = $("#template-row").clone(true, true); + row.show(); + row.find('.key').attr('id', 'id_attribute_key_' + row_number); + row.find('.value').attr('id', 'id_attribute_value_' + row_number); + row.find('.key').attr('name', 'attribute_key_' + row_number); + row.find('.value').attr('name', 'attribute_value_' + row_number); + row_number += 1; + body.append(row); + row.find(".key").autocomplete(keyAutocompleteConfig); + row.find(".value").autocomplete(valueAutocompleteConfig); + }); +$("a.delete-row").click( + function (e) { + e.preventDefault(); + $(this).closest('tr').remove(); + }); +var keyAutocompleteConfig = { + source: attr_name_completion_url + }; +var valueAutocompleteConfig = { + source: function (request, response) { + var attrName = this.element.closest('tr').find('input.key').val(); + $.getJSON( + attr_value_completion_url, + { + 'name': attrName, + 'term': request.term + }, + function (data) { + response(data); + } + ); + } + }; +$("tbody .key").autocomplete(keyAutocompleteConfig); +$("tbody .value").autocomplete(valueAutocompleteConfig); +}); \ No newline at end of file === added file 'dashboard_app/templates/dashboard_app/filter_add.html' --- dashboard_app/templates/dashboard_app/filter_add.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_add.html 2012-09-03 00:09:09 +0000 @@ -0,0 +1,26 @@ +{% extends "dashboard_app/_content.html" %} +{% load i18n %} + +{% block extrahead %} +{{ block.super }} +{{ form.media }} +{% endblock %} + +{% block content %} +{% if form.instance.pk %} +

[BETA] Edit filter “{{ form.instance.name }}”…

+{% else %} +

[BETA] Add new filter…

+{% endif %} + +
+ {% csrf_token %} + {% include "dashboard_app/filter_form.html" %} +{% if form.instance.pk %} + +{% else %} + +{% endif %} +
+ +{% endblock %} === added file 'dashboard_app/templates/dashboard_app/filter_delete.html' --- dashboard_app/templates/dashboard_app/filter_delete.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_delete.html 2012-09-03 00:09:09 +0000 @@ -0,0 +1,14 @@ +{% extends "dashboard_app/_content.html" %} +{% load i18n %} + +{% block content %} +

[BETA] Delete filter {{ filter.name }}

+ +
+ {% csrf_token %} + Do you really want to delete the filter {{ filter.name }}? + + +
+ +{% endblock %} === added file 'dashboard_app/templates/dashboard_app/filter_detail.html' --- dashboard_app/templates/dashboard_app/filter_detail.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_detail.html 2012-09-03 00:09:09 +0000 @@ -0,0 +1,30 @@ +{% extends "dashboard_app/_content.html" %} +{% load i18n %} +{% load django_tables2 %} + +{% block content %} + +

[BETA] Filter {{ filter.name }}

+ +{% include "dashboard_app/filter_summary.html" with summary_data=filter.summary_data %} + +{% if filter.owner == request.user %} +

+ You can edit + or delete this filter. +

+{% endif %} + +{% if subscription %} +

+ Manage your subscription to this filter. +

+{% else %} +

+ Subscribe to this filter. +

+{% endif %} + +{% render_table filter_table %} + +{% endblock %} === added file 'dashboard_app/templates/dashboard_app/filter_form.html' --- dashboard_app/templates/dashboard_app/filter_form.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_form.html 2012-08-16 00:14:22 +0000 @@ -0,0 +1,98 @@ +

+ A filter matches test runs by a number of criteria. +

+ {{ form.non_field_errors }} +
+
+ Metadata: +
+
+ {{ form.name.errors }} + {{ form.name.label_tag }}: {{ form.name }} +
{{ form.name.help_text|safe }} +
{{ form.public.errors }} + {{ form.public }}{{ form.public.label_tag }} +
{{ form.public.help_text|safe }} +
+
+ {{ form.bundle_streams.label_tag }}: +
+
+ {{ form.bundle_streams.errors }} +
{{ form.bundle_streams }}
+
{{ form.bundle_streams.help_text|safe }}
+
+
+ Attributes: +
+
+ + + + + + + + + {% for attr in form.attributes %} + + + + + + {% endfor %} + + + + + + + + + + + +
+ Name + + Value +
+ + + + + remove +
+ Add a required attribute +
+
+ A filter can be limited to test runs with particular values for particular attributes. + +
+
+ Test and test case: +
+
+ {{ 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. + +
+
=== added file 'dashboard_app/templates/dashboard_app/filter_preview.html' --- dashboard_app/templates/dashboard_app/filter_preview.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_preview.html 2012-09-03 00:09:09 +0000 @@ -0,0 +1,53 @@ +{% extends "dashboard_app/_content.html" %} +{% load i18n %} +{% load django_tables2 %} + +{% block extrahead %} +{{ block.super }} +{{ form.media }} +{% endblock %} + +{% block content %} +{% if form.instance.pk %} +

[BETA] Previewing changes to filter “{{ form.instance.name }}”

+{% else %} +

[BETA] Previewing new filter “{{ form.name.value }}”

+{% endif %} + +{% include "dashboard_app/filter_summary.html" with summary_data=form.summary_data %} + +

+ These are the results matched by your filter. +

+ +{% render_table table %} + +

+ +

+

+ If this is what you expected, you can + {% if form.instance.pk %} + to the filter. + {% else %} + the filter. + {% endif %} +

+

+ Otherwise, you can edit it. +

+ {% csrf_token %} + +
+ + + +{% endblock %} === added file 'dashboard_app/templates/dashboard_app/filter_subscribe.html' --- dashboard_app/templates/dashboard_app/filter_subscribe.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_subscribe.html 2012-09-03 00:09:09 +0000 @@ -0,0 +1,26 @@ +{% extends "dashboard_app/_content.html" %} +{% load i18n %} +{% load django_tables2 %} + +{% block content %} + +

[BETA] Subscribe to filter {{ filter.name }}

+ +
+ {% csrf_token %} +

+ {{ form.level.help_text|safe }} + {{ form.level.label_tag }}: + {{ form.level }} +

+

+ {% if form.instance.pk %} + + + {% else %} + + {% endif %} +

+
+ +{% endblock %} === added file 'dashboard_app/templates/dashboard_app/filter_subscription_mail.txt' --- dashboard_app/templates/dashboard_app/filter_subscription_mail.txt 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_subscription_mail.txt 2012-08-23 22:50:32 +0000 @@ -0,0 +1,13 @@ +Dear {{ user.first_name }} {{ user.last_name }}, + +The bundle {{ bundle.content_filename }} was uploaded at {{ bundle.uploaded_on|date:"Y-m-d H:i:s" }} by {% if bundle.uploaded_by %}{{ bundle.uploaded_by }}{% else %}an anonymous user{% endif %}. + +It matched the following filters that you are subscribed to: + +{% for match in matches %}{{ match.format_for_mail }}{% endfor %} +You can see more details at: + + {{ url_prefix }}{{ bundle.get_absolute_url }} + +LAVA +Linaro Automated Validation Architecture === added file 'dashboard_app/templates/dashboard_app/filter_summary.html' --- dashboard_app/templates/dashboard_app/filter_summary.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filter_summary.html 2012-08-17 03:51:42 +0000 @@ -0,0 +1,38 @@ + + + + + +{% if summary_data.attributes %} + + + + +{% endif %} + + + + +
+ Bundle streams + + {% for stream in summary_data.bundle_streams.all %} + {{stream.pathname}}{% if not forloop.last %}, {% endif %} + {% endfor %} +
+ Attributes + + {% for a in summary_data.attributes %} + {{ a.0 }} == {{ a.1 }}
+ {% endfor %} +
+ Test case + + {% if summary_data.test_case %} + {{ summary_data.test }}:{{ summary_data.test_case }} + {% elif summary_data.test %} + {{ summary_data.test }}:<any> + {% else %} + <any>:<any> + {% endif %} +
=== added file 'dashboard_app/templates/dashboard_app/filters_list.html' --- dashboard_app/templates/dashboard_app/filters_list.html 1970-01-01 00:00:00 +0000 +++ dashboard_app/templates/dashboard_app/filters_list.html 2012-09-03 00:09:09 +0000 @@ -0,0 +1,35 @@ +{% extends "dashboard_app/_content.html" %} +{% load i18n %} +{% load django_tables2 %} + +{% block content %} +

[BETA] Filters

+ +

+ A filter matches test runs by a number of criteria. +

+ +{% if user_filters_table %} + +

Your Filters

+ +{% render_table user_filters_table %} + +

+ Add new filter… +

+ +{% else %} + +

+ Please log in to see and manage your filters. +

+ +{% endif %} + +

Public Filters

+ +{% render_table public_filters_table %} + + +{% endblock %} === modified file 'dashboard_app/urls.py' --- dashboard_app/urls.py 2012-07-13 04:12:52 +0000 +++ dashboard_app/urls.py 2012-08-23 22:50:32 +0000 @@ -35,6 +35,17 @@ url(r'^reports/(?P[a-zA-Z0-9-_]+)/$', 'report_detail'), url(r'^tests/$', 'test_list'), url(r'^tests/(?P[^/]+)/$', 'test_detail'), + url(r'^filters/$', 'filters_list'), + url(r'^filters/\+add$', 'filter_add'), + url(r'^filters/\+add-preview-json$', 'filter_preview_json'), + url(r'^filters/\+add-cases-for-test-json$', 'filter_add_cases_for_test_json'), + url(r'^filters/\+attribute-name-completion-json$', 'filter_attr_name_completion_json'), + url(r'^filters/\+attribute-value-completion-json$', 'filter_attr_value_completion_json'), + url(r'^filters/~(?P[a-zA-Z0-9-_]+)/(?P[a-zA-Z0-9-_]+)$', 'filter_detail'), + url(r'^filters/~(?P[a-zA-Z0-9-_]+)/(?P[a-zA-Z0-9-_]+)/json$', 'filter_json'), + url(r'^filters/~(?P[a-zA-Z0-9-_]+)/(?P[a-zA-Z0-9-_]+)/\+edit$', 'filter_edit'), + url(r'^filters/~(?P[a-zA-Z0-9-_]+)/(?P[a-zA-Z0-9-_]+)/\+subscribe$', 'filter_subscribe'), + url(r'^filters/~(?P[a-zA-Z0-9-_]+)/(?P[a-zA-Z0-9-_]+)/\+delete$', 'filter_delete'), url(r'^xml-rpc/$', linaro_django_xmlrpc.views.handler, name='dashboard_app.views.dashboard_xml_rpc_handler', kwargs={ === modified file 'dashboard_app/views.py' --- dashboard_app/views.py 2012-08-30 02:12:14 +0000 +++ dashboard_app/views.py 2012-09-03 00:16:21 +0000 @@ -23,14 +23,20 @@ import re import json +from django.conf import settings +from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.auth.decorators import login_required from django.contrib.sites.models import Site +from django.core.exceptions import PermissionDenied, ValidationError from django.core.urlresolvers import reverse from django.db.models.manager import Manager from django.db.models.query import QuerySet +from django import forms 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 +from django.template import Template, Context +from django.utils.html import escape from django.utils.safestring import mark_safe from django.views.decorators.http import require_POST from django.views.generic.list_detail import object_list, object_detail @@ -53,10 +59,14 @@ Image, ImageSet, LaunchpadBug, + NamedAttribute, Tag, Test, + TestCase, TestResult, TestRun, + TestRunFilter, + TestRunFilterSubscription, TestingEffort, ) @@ -434,6 +444,439 @@ searchable_columns = ['test_case__test_case_id'] +class UserFiltersTable(DataTablesTable): + + name = TemplateColumn(''' + {{ record.name }} + ''') + + bundle_streams = TemplateColumn(''' + {% for r in record.bundle_streams.all %} + {{r.pathname}}
+ {% endfor %} + ''') + + attributes = TemplateColumn(''' + {% for a in record.attributes.all %} + {{ a }}
+ {% endfor %} + ''') + + test = TemplateColumn(''' + {% if record.test_case %} + {{ record.test }}:{{ record.test_case }} + {% elif record.test %} + {{ record.test }}:<any> + {% else %} + <any>:<any> + {% endif %} + ''') + + subscription = Column() + def render_subscription(self, record): + try: + sub = TestRunFilterSubscription.objects.get( + user=self.user, filter=record) + except TestRunFilterSubscription.DoesNotExist: + return "None" + else: + return sub.get_level_display() + + public = Column() + + def get_queryset(self, user): + return TestRunFilter.objects.filter(owner=user) + + +class PublicFiltersTable(UserFiltersTable): + + name = TemplateColumn(''' + ~{{ record.owner.username }}/{{ record.name }} + ''') + + def __init__(self, *args, **kw): + super(PublicFiltersTable, self).__init__(*args, **kw) + del self.base_columns['public'] + + def get_queryset(self): + return TestRunFilter.objects.filter(public=True) + + +@BreadCrumb("Filters and Subscriptions", parent=index) +def filters_list(request): + public_filters_table = PublicFiltersTable("public-filters", None) + if request.user.is_authenticated(): + public_filters_table.user = request.user + user_filters_table = UserFiltersTable("user-filters", None, params=(request.user,)) + user_filters_table.user = request.user + else: + user_filters_table = None + del public_filters_table.base_columns['subscription'] + + return render_to_response( + 'dashboard_app/filters_list.html', { + 'user_filters_table': user_filters_table, + 'public_filters_table': public_filters_table, + 'bread_crumb_trail': BreadCrumbTrail.leading_to( + filters_list), + }, RequestContext(request) + ) + + +class SpecificCaseColumn(Column): + def render(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)) + + +class BundleColumn(Column): + def render(self, record): + return mark_safe('' + escape(record.bundle.content_filename) + '') + + +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'] + 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) + def get_queryset(self, user, filter): + return filter.get_test_runs(user) + + datatable_opts = { + "sPaginationType": "full_numbers", + "iDisplayLength": 25, + } + + +def filter_json(request, username, name): + filter = TestRunFilter.objects.get(owner__username=username, name=name) + return FilterTable.json(request, params=(request.user, filter)) + + +class FilterPreviewTable(FilterTable): + def get_queryset(self, user, form): + return form.get_test_runs(user) + + datatable_opts = FilterTable.datatable_opts.copy() + datatable_opts.update({ + "iDisplayLength": 10, + }) + + +def filter_preview_json(request): + try: + filter = TestRunFilter.objects.get(owner=request.user, name=request.GET['name']) + except TestRunFilter.DoesNotExist: + filter = None + form = TestRunFilterForm(request.user, request.GET, instance=filter) + if not form.is_valid(): + raise ValidationError(str(form.errors)) + return FilterPreviewTable.json(request, params=(request.user, form)) + + +@BreadCrumb("Filter ~{username}/{name}", parent=filters_list, needs=['username', 'name']) +def filter_detail(request, username, name): + filter = TestRunFilter.objects.get(owner__username=username, name=name) + if not filter.public and filter.owner != request.user: + raise PermissionDenied() + if not request.user.is_authenticated(): + subscription = None + else: + try: + subscription = TestRunFilterSubscription.objects.get( + user=request.user, filter=filter) + except TestRunFilterSubscription.DoesNotExist: + subscription = None + return render_to_response( + 'dashboard_app/filter_detail.html', { + 'filter': filter, + 'subscription': subscription, + 'filter_table': FilterTable( + "filter-table", + reverse(filter_json, kwargs=dict(username=username, name=name)), + params=(request.user, filter)), + 'bread_crumb_trail': BreadCrumbTrail.leading_to( + filter_detail, name=name, username=username), + }, RequestContext(request) + ) + + +class TestRunFilterSubscriptionForm(forms.ModelForm): + class Meta: + model = TestRunFilterSubscription + fields = ('level',) + def __init__(self, filter, user, *args, **kwargs): + super(TestRunFilterSubscriptionForm, self).__init__(*args, **kwargs) + self.instance.filter = filter + self.instance.user = user + + +@BreadCrumb("Manage Subscription", parent=filter_detail, needs=['name', 'username']) +@login_required +def filter_subscribe(request, username, name): + filter = TestRunFilter.objects.get(owner__username=username, name=name) + if not filter.public and filter.owner != request.user: + raise PermissionDenied() + try: + subscription = TestRunFilterSubscription.objects.get( + user=request.user, filter=filter) + except TestRunFilterSubscription.DoesNotExist: + subscription = None + if request.method == "POST": + form = TestRunFilterSubscriptionForm( + filter, request.user, request.POST, instance=subscription) + if form.is_valid(): + if 'unsubscribe' in request.POST: + subscription.delete() + else: + form.save() + return HttpResponseRedirect(filter.get_absolute_url()) + else: + form = TestRunFilterSubscriptionForm( + filter, request.user, instance=subscription) + return render_to_response( + 'dashboard_app/filter_subscribe.html', { + 'filter': filter, + 'form': form, + 'subscription': subscription, + 'bread_crumb_trail': BreadCrumbTrail.leading_to( + filter_subscribe, name=name, username=username), + }, RequestContext(request) + ) + + +test_run_filter_head = ''' + + + + +''' + + +class TestRunFilterForm(forms.ModelForm): + class Meta: + model = TestRunFilter + exclude = ('owner',) + widgets = { + 'bundle_streams': FilteredSelectMultiple("Bundle Streams", False), + } + + @property + def media(self): + super_media = str(super(TestRunFilterForm, self).media) + return mark_safe(Template(test_run_filter_head).render( + 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: + self.instance.validate_unique() + except ValidationError, e: + if e.message_dict.values() == [[ + u'Test run filter with this Owner and Name already exists.']]: + raise ValidationError("You already have a filter with this name") + else: + raise + + def save(self, commit=True, **kwargs): + instance = super(TestRunFilterForm, self).save(commit=commit, **kwargs) + if commit: + instance.attributes.all().delete() + for (name, value) in self.attributes: + instance.attributes.create(name=name, value=value) + return instance + + @property + def summary_data(self): + data = self.cleaned_data.copy() + data['attributes'] = self.attributes + return data + + def __init__(self, user, *args, **kwargs): + super(TestRunFilterForm, self).__init__(*args, **kwargs) + self.instance.owner = user + self.fields['bundle_streams'].queryset = BundleStream.objects.accessible_by_principal(user) + 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') + + @property + def attributes(self): + if not self.is_bound and self.instance.pk: + return self.instance.attributes.values_list('name', 'value') + else: + attributes = [] + for (var, value) in self.data.iteritems(): + if var.startswith('attribute_key_'): + index = int(var[len('attribute_key_'):]) + attr_value = self.data['attribute_value_' + str(index)] + attributes.append((index, value, attr_value)) + + attributes.sort() + attributes = [a[1:] for a in attributes] + return attributes + + def get_test_runs(self, user): + assert self.is_valid(), self.errors + filter = self.save(commit=False) + return filter.get_test_runs_impl( + user, self.cleaned_data['bundle_streams'], self.attributes) + + +def filter_form(request, bread_crumb_trail, instance=None): + if request.method == 'POST': + form = TestRunFilterForm(request.user, request.POST, instance=instance) + + if form.is_valid(): + if 'save' in request.POST: + filter = form.save() + return HttpResponseRedirect(filter.get_absolute_url()) + else: + c = request.POST.copy() + c.pop('csrfmiddlewaretoken', None) + return render_to_response( + 'dashboard_app/filter_preview.html', { + 'bread_crumb_trail': bread_crumb_trail, + 'form': form, + 'table': FilterPreviewTable( + 'filter-preview', + reverse(filter_preview_json) + '?' + c.urlencode(), + params=(request.user, form)), + }, RequestContext(request)) + else: + form = TestRunFilterForm(request.user, instance=instance) + return render_to_response( + 'dashboard_app/filter_add.html', { + 'bread_crumb_trail': bread_crumb_trail, + 'form': form, + }, RequestContext(request)) + + +@BreadCrumb("Add new filter", parent=filters_list) +def filter_add(request): + return filter_form( + request, + BreadCrumbTrail.leading_to(filter_add)) + + +@BreadCrumb("Edit", parent=filter_detail, needs=['name', 'username']) +def filter_edit(request, username, name): + if request.user.username != username: + raise PermissionDenied() + filter = TestRunFilter.objects.get(owner=request.user, name=name) + return filter_form( + request, + BreadCrumbTrail.leading_to(filter_edit, name=name, username=username), + instance=filter) + + +@BreadCrumb("Delete", parent=filter_detail, needs=['name', 'username']) +def filter_delete(request, username, name): + if request.user.username != username: + raise PermissionDenied() + filter = TestRunFilter.objects.get(owner=request.user, name=name) + if request.method == "POST": + if 'yes' in request.POST: + filter.delete() + return HttpResponseRedirect(reverse(filters_list)) + else: + return HttpResponseRedirect(filter.get_absolute_url()) + return render_to_response( + 'dashboard_app/filter_delete.html', { + 'bread_crumb_trail': BreadCrumbTrail.leading_to(filter_delete, name=name, username=username), + 'filter': filter, + }, RequestContext(request)) + + +def filter_add_cases_for_test_json(request): + test = Test.objects.get(test_id=request.GET['test']) + result = TestCase.objects.filter(test=test).order_by('test_case_id').values('test_case_id', 'id') + return HttpResponse( + json.dumps(list(result)), + mimetype='application/json') + + +def filter_attr_name_completion_json(request): + term = request.GET['term'] + result = NamedAttribute.objects.filter( + name__startswith=term).distinct('name').order_by('name').values_list('name', flat=True) + return HttpResponse( + json.dumps(list(result)), + mimetype='application/json') + + +def filter_attr_value_completion_json(request): + name = request.GET['name'] + term = request.GET['term'] + result = NamedAttribute.objects.filter( + name=name, + value__startswith=term).distinct('value').order_by('value').values_list('value', flat=True) + return HttpResponse( + json.dumps(list(result)), + mimetype='application/json') + + def test_run_detail_test_json(request, pathname, content_sha1, analyzer_assigned_uuid): test_run = get_restricted_object_or_404( TestRun, lambda test_run: test_run.bundle.bundle_stream, === modified file 'doc/changes.rst' --- doc/changes.rst 2012-08-28 05:25:49 +0000 +++ doc/changes.rst 2012-08-30 00:00:01 +0000 @@ -6,6 +6,7 @@ Version 0.21 ============ * Unreleased +* Add the concept of a test run filter. .. _version_0_20: @@ -21,7 +22,6 @@ Version 0.19 ============ - * Add image status views and models for use by the QA services team. * Allow linking test runs to launchpad bugs from the image status view.