From patchwork Wed Sep 26 20:56:11 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael-Doyle Hudson X-Patchwork-Id: 11745 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 8252023E54 for ; Wed, 26 Sep 2012 20:56:15 +0000 (UTC) Received: from mail-ie0-f180.google.com (mail-ie0-f180.google.com [209.85.223.180]) by fiordland.canonical.com (Postfix) with ESMTP id BE4FEA18BD3 for ; Wed, 26 Sep 2012 20:56:14 +0000 (UTC) Received: by ieje10 with SMTP id e10so2534817iej.11 for ; Wed, 26 Sep 2012 13:56: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=7IvmV6EbO/50UbCoWNxN+lsew1GbBJhoIB4PizjMAa8=; b=g1Y3htfIwuMhgk0CmN2NSrlLAKo+sQ8a0P7JID6qatHNYDCuzh7hYXBieq2T/8yfHK PZC63MBCxkdblyjJz72HeyejzLPxoX2IT/zHoo2gKIjf7w+5B6TqFisjkhM6XY0EKSwF 8NBGiO3xM+hQ1BzQKqfYdZqgYtlWIfD46jXlhU/axb6sAsSWbcvgtRPiwiKqG34q6x9E WyGs6qHT+V6+N9JtPBmduXcwp3bcPVQn60+MWzDdIssA1JGr7ixoGA9O8GxC+/f9woNg sE12ncDaKkm2A6EnWlBh70RHx6Fxnp0cixejIFCqUWRLudDU8aVc4Z3qam52t/Zmnfuy r+8w== Received: by 10.50.150.198 with SMTP id uk6mr1665203igb.43.1348692974212; Wed, 26 Sep 2012 13:56: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 ex8csp379779igc; Wed, 26 Sep 2012 13:56:13 -0700 (PDT) Received: by 10.216.72.5 with SMTP id s5mr245532wed.154.1348692972389; Wed, 26 Sep 2012 13:56:12 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id h21si4014172wea.22.2012.09.26.13.56.11 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 26 Sep 2012 13:56: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 1TGyeR-0005My-Nt for ; Wed, 26 Sep 2012 20:56:11 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id A0D0BE0128 for ; Wed, 26 Sep 2012 20:56:11 +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: 350 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-dashboard/trunk] Rev 350: a better way of basing image reports on filters Message-Id: <20120926205611.24014.93393.launchpad@ackee.canonical.com> Date: Wed, 26 Sep 2012 20:56:11 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="16033"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 225024b7748d34f7c020b628f69ab52c7d621513 X-Gm-Message-State: ALoCoQltgfQgj3RAtJBuW3qG2tcjvidmBAHR9gNjuP3HROScAR45AoDrGVGHs0zE6glAKnKNu/Rf Merge authors: Michael Hudson-Doyle (mwhudson) Related merge proposals: https://code.launchpad.net/~mwhudson/lava-dashboard/proper-filter-based-image-reports/+merge/126355 proposed by: Michael Hudson-Doyle (mwhudson) review: Approve - Andy Doan (doanac) ------------------------------------------------------------ revno: 350 [merge] committer: Michael Hudson-Doyle branch nick: trunk timestamp: Thu 2012-09-27 08:54:46 +1200 message: a better way of basing image reports on filters added: dashboard_app/migrations/0024_auto__del_imageattribute__del_field_image_uploaded_by__del_field_image.py dashboard_app/static/css/wider-filter-horizontal.css modified: dashboard_app/admin.py dashboard_app/models.py dashboard_app/templates/dashboard_app/image-report.html dashboard_app/views.py --- lp:lava-dashboard https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk You are subscribed to branch lp:lava-dashboard. To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk/+edit-subscription === modified file 'dashboard_app/admin.py' --- dashboard_app/admin.py 2012-09-24 03:38:56 +0000 +++ dashboard_app/admin.py 2012-09-25 22:40:20 +0000 @@ -33,7 +33,6 @@ BundleStream, HardwareDevice, Image, - ImageAttribute, ImageSet, LaunchpadBug, NamedAttribute, @@ -183,27 +182,16 @@ list_display = ('__unicode__', 'project') -class ImageAttributeInline(admin.TabularInline): - model = ImageAttribute - verbose_name = 'required metadata attribute' - verbose_name_plural = 'required metadata attributes' - - class ImageAdmin(admin.ModelAdmin): - filter_horizontal = ['bundle_streams'] - inlines = [ImageAttributeInline] save_as = True class ImageSetAdmin(admin.ModelAdmin): - filter_horizontal = ['filters'] - def formfield_for_manytomany(self, db_field, request, **kwargs): - field = super(ImageSetAdmin, self).formfield_for_manytomany( - db_field, request, **kwargs) - print db_field - if db_field.name == 'filters': - field.queryset = TestRunFilter.objects.filter(enable_as_image=True).order_by('name') - return field + class Media: + css = { + "all": ("dashboard_app/css/wider-filter-horizontal.css",) + } + filter_horizontal = ['images'] save_as = True === added file 'dashboard_app/migrations/0024_auto__del_imageattribute__del_field_image_uploaded_by__del_field_image.py' --- dashboard_app/migrations/0024_auto__del_imageattribute__del_field_image_uploaded_by__del_field_image.py 1970-01-01 00:00:00 +0000 +++ dashboard_app/migrations/0024_auto__del_imageattribute__del_field_image_uploaded_by__del_field_image.py 2012-09-25 22:21:40 +0000 @@ -0,0 +1,325 @@ +# -*- 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): + # Deleting model 'ImageAttribute' + db.delete_table('dashboard_app_imageattribute') + + # Deleting field 'Image.uploaded_by' + db.delete_column('dashboard_app_image', 'uploaded_by_id') + + # Deleting field 'Image.build_number_attribute' + db.delete_column('dashboard_app_image', 'build_number_attribute') + + # Adding field 'Image.filter' + db.add_column('dashboard_app_image', 'filter', + self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', null=True, to=orm['dashboard_app.TestRunFilter']), + keep_default=False) + + # Removing M2M table for field bundle_streams on 'Image' + db.delete_table('dashboard_app_image_bundle_streams') + + # Removing M2M table for field filters on 'ImageSet' + db.delete_table('dashboard_app_imageset_filters') + + # Deleting field 'TestRunFilter.enable_as_image' + db.delete_column('dashboard_app_testrunfilter', 'enable_as_image') + + + def backwards(self, orm): + # Adding model 'ImageAttribute' + db.create_table('dashboard_app_imageattribute', ( + ('image', self.gf('django.db.models.fields.related.ForeignKey')(related_name='required_attributes', to=orm['dashboard_app.Image'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('value', self.gf('django.db.models.fields.CharField')(max_length=1024)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=1024)), + )) + db.send_create_signal('dashboard_app', ['ImageAttribute']) + + # Adding field 'Image.uploaded_by' + db.add_column('dashboard_app_image', 'uploaded_by', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True), + keep_default=False) + + + # User chose to not deal with backwards NULL issues for 'Image.build_number_attribute' + raise RuntimeError("Cannot reverse this migration. 'Image.build_number_attribute' and its values cannot be restored.") + # Deleting field 'Image.filter' + db.delete_column('dashboard_app_image', 'filter_id') + + # Adding M2M table for field bundle_streams on 'Image' + db.create_table('dashboard_app_image_bundle_streams', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('image', models.ForeignKey(orm['dashboard_app.image'], null=False)), + ('bundlestream', models.ForeignKey(orm['dashboard_app.bundlestream'], null=False)) + )) + db.create_unique('dashboard_app_image_bundle_streams', ['image_id', 'bundlestream_id']) + + # Adding M2M table for field filters on 'ImageSet' + db.create_table('dashboard_app_imageset_filters', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('imageset', models.ForeignKey(orm['dashboard_app.imageset'], null=False)), + ('testrunfilter', models.ForeignKey(orm['dashboard_app.testrunfilter'], null=False)) + )) + db.create_unique('dashboard_app_imageset_filters', ['imageset_id', 'testrunfilter_id']) + + # Adding field 'TestRunFilter.enable_as_image' + db.add_column('dashboard_app_testrunfilter', 'enable_as_image', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'dashboard_app.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}), + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'}) + }, + 'dashboard_app.bundle': { + 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'}, + '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}), + '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}), + 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}), + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}), + 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) + }, + 'dashboard_app.bundledeserializationerror': { + 'Meta': {'object_name': 'BundleDeserializationError'}, + 'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}), + 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'}) + }, + 'dashboard_app.bundlestream': { + 'Meta': {'object_name': 'BundleStream'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.hardwaredevice': { + 'Meta': {'object_name': 'HardwareDevice'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'dashboard_app.image': { + 'Meta': {'object_name': 'Image'}, + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['dashboard_app.TestRunFilter']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + 'dashboard_app.imageset': { + 'Meta': {'object_name': 'ImageSet'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'}) + }, + 'dashboard_app.launchpadbug': { + 'Meta': {'object_name': 'LaunchpadBug'}, + 'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"}) + }, + 'dashboard_app.namedattribute': { + 'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.TextField', [], {}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'dashboard_app.softwarepackage': { + 'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'dashboard_app.softwarepackagescratch': { + 'Meta': {'object_name': 'SoftwarePackageScratch'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'dashboard_app.softwaresource': { + 'Meta': {'object_name': 'SoftwareSource'}, + 'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'dashboard_app.tag': { + 'Meta': {'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}) + }, + 'dashboard_app.test': { + 'Meta': {'object_name': 'Test'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}) + }, + 'dashboard_app.testcase': { + 'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}), + 'test_case_id': ('django.db.models.fields.TextField', [], {}), + 'units': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'dashboard_app.testingeffort': { + 'Meta': {'object_name': 'TestingEffort'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'testing_efforts'", 'to': "orm['lava_projects.Project']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'testing_efforts'", 'symmetrical': 'False', 'to': "orm['dashboard_app.Tag']"}) + }, + 'dashboard_app.testresult': { + 'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'}, + '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}), + 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'dashboard_app.testrun': { + 'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'}, + 'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}), + 'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}), + 'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}), + 'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}), + 'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}), + 'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}), + 'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'dashboard_app.testrundenormalization': { + 'Meta': {'object_name': 'TestRunDenormalization'}, + 'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"}) + }, + 'dashboard_app.testrunfilter': { + 'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'}, + 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['auth.User']"}) + }, + 'dashboard_app.testrunfilterattribute': { + 'Meta': {'object_name': 'TestRunFilterAttribute'}, + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + 'dashboard_app.testrunfiltersubscription': { + 'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'}, + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'dashboard_app.testrunfiltertest': { + 'Meta': {'object_name': 'TestRunFilterTest'}, + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tests'", 'to': "orm['dashboard_app.TestRunFilter']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'index': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.Test']"}) + }, + 'dashboard_app.testrunfiltertestcase': { + 'Meta': {'object_name': 'TestRunFilterTestCase'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'index': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cases'", 'to': "orm['dashboard_app.TestRunFilterTest']"}), + 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.TestCase']"}) + }, + 'lava_projects.project': { + 'Meta': {'object_name': 'Project'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}), + 'is_aggregate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}), + 'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['dashboard_app'] \ No newline at end of file === modified file 'dashboard_app/models.py' --- dashboard_app/models.py 2012-09-24 03:50:34 +0000 +++ dashboard_app/models.py 2012-09-25 23:31:26 +0000 @@ -1432,84 +1432,15 @@ tags__in=self.tags.all()) -class ImageAttribute(models.Model): - - name = models.CharField(max_length=1024) - value = models.CharField(max_length=1024) - - image = models.ForeignKey("Image", related_name="required_attributes") - - def __unicode__(self): - return '%s = %s' % (self.name, self.value) - class Image(models.Model): name = models.SlugField(max_length=1024, unique=True) - build_number_attribute = models.CharField(max_length=1024) - - bundle_streams = models.ManyToManyField(BundleStream) - - uploaded_by = models.ForeignKey(User, null=True, blank=True) + filter = models.ForeignKey("TestRunFilter", related_name='+', null=True) def __unicode__(self): - return self.name - - def _get_bundles(self, user): - accessible_bundles = BundleStream.objects.accessible_by_principal( - user) - args = [models.Q(bundle_stream__in=accessible_bundles)] - if self.bundle_streams.exists(): - args += [models.Q(bundle_stream__in=self.bundle_streams.all())] - if self.uploaded_by: - args += [models.Q(uploaded_by=self.uploaded_by)] - bundles = Bundle.objects.filter(*args) - - # This is a little tricky. We want to AND together the conditions - # that the attribute matches, but with the Django ORM we can only join - # the attribute table once per query so we put each condition in a - # nested query, so for example instead of something like this: - # - # select * from bundle - # where - # and - # and bundle.id in - # (select * from bundle - # where ') + return '%s, based on %s' % (self.name, owner_name) @models.permalink def get_absolute_url(self): @@ -1520,9 +1451,7 @@ name = models.CharField(max_length=1024, unique=True) - images = models.ManyToManyField(Image, help_text="This field is now obsolete") - - filters = models.ManyToManyField("TestRunFilter") + images = models.ManyToManyField(Image) def __unicode__(self): return self.name @@ -1621,14 +1550,15 @@ """Wrap a QuerySet and construct FilterMatchs from what the wrapped query set returns. - Just enough of the QuerySet API to work with DataTable (i.e. ordering and - slicing).""" + Just enough of the QuerySet API to work with DataTable (i.e. pretend + ordering and real slicing).""" model = TestRun - def __init__(self, queryset, filter_data): + def __init__(self, queryset, filter_data, prefetch_related): self.queryset = queryset self.filter_data = filter_data + self.prefetch_related = prefetch_related if filter_data['build_number_attribute']: self.key = 'build_number' self.key_name = 'Build' @@ -1642,7 +1572,8 @@ test_run_ids.update(datum['id__arrayagg']) r = [] trs = TestRun.objects.filter(id__in=test_run_ids).select_related( - 'denormalization', 'bundle', 'bundle__bundle_stream', 'test') + 'denormalization', 'bundle', 'bundle__bundle_stream', 'test').prefetch_related( + *self.prefetch_related) trs_by_id = {} for tr in trs: trs_by_id[tr.id] = tr @@ -1690,7 +1621,7 @@ return iter(r) def _wrap(self, queryset, **kw): - return self.__class__(queryset, self.filter_data, **kw) + return self.__class__(queryset, self.filter_data, self.prefetch_related, **kw) def order_by(self, *args): # the generic tables code calls this even when it shouldn't... @@ -1771,6 +1702,11 @@ 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.")) + + @property + def owner_name(self): + return '~%s/%s' % (self.owner.username, self.name) + class Meta: unique_together = (('owner', 'name')) @@ -1788,8 +1724,6 @@ User, null=True, blank=True, related_name='+', help_text="Only consider bundles uploaded by this user") - enable_as_image = models.BooleanField(default=False) - @property def summary_data(self): return { @@ -1811,7 +1745,7 @@ # and testrun has attribute with key = keyN and value = valueN # and testrun has any of the tests/testcases requested - def get_test_runs_impl(self, user, bundle_streams, attributes, tests): + def get_test_runs_impl(self, user, bundle_streams, attributes, tests, prefetch_related=[]): accessible_bundle_streams = BundleStream.objects.accessible_by_principal( user) bs_ids = [bs.id for bs in set(accessible_bundle_streams) & set(bundle_streams)] @@ -1868,7 +1802,7 @@ 'build_number_attribute': self.build_number_attribute, } - return MatchMakingQuerySet(testruns, filter_data) + return MatchMakingQuerySet(testruns, filter_data, prefetch_related) # given bundle: # select from filter @@ -1933,12 +1867,13 @@ matches.append(match) return matches - def get_test_runs(self, user): + def get_test_runs(self, user, prefetch_related=[]): return self.get_test_runs_impl( user, self.bundle_streams.all(), self.attributes.values_list('name', 'value'), - self.tests.all()) + self.tests.all(), + prefetch_related) @models.permalink def get_absolute_url(self): === added file 'dashboard_app/static/css/wider-filter-horizontal.css' --- dashboard_app/static/css/wider-filter-horizontal.css 1970-01-01 00:00:00 +0000 +++ dashboard_app/static/css/wider-filter-horizontal.css 2012-09-25 22:38:13 +0000 @@ -0,0 +1,16 @@ +.selector { + width: 1000px; + float: left; +} + +.selector select { + width: 480px; + height: 17.2em; +} + +.selector-available, .selector-chosen { + float: left; + width: 480px; + text-align: center; + margin-bottom: 5px; +} === modified file 'dashboard_app/templates/dashboard_app/image-report.html' --- dashboard_app/templates/dashboard_app/image-report.html 2012-07-16 22:51:19 +0000 +++ dashboard_app/templates/dashboard_app/image-report.html 2012-09-25 23:31:26 +0000 @@ -41,18 +41,18 @@ - {% for bundle in bundles %} + {% for col in cols %} {% endfor %} - {% for bundle in bundles %} + {% for col in cols %} {% endfor %} === modified file 'dashboard_app/views.py' --- dashboard_app/views.py 2012-09-24 03:47:27 +0000 +++ dashboard_app/views.py 2012-09-25 23:49:47 +0000 @@ -1485,17 +1485,20 @@ @BreadCrumb("Image Reports", parent=index) def image_report_list(request): - imagesets = ImageSet.objects.all() + imagesets = ImageSet.objects.filter() imagesets_data = [] for imageset in imagesets: images_data = [] - for filter in imageset.filters.all(): - image_data = { - 'name': filter.name, - 'bundle_count': filter.get_test_runs(request.user).count(), - 'link': filter.name, - } - images_data.append(image_data) + for image in imageset.images.all(): + # Migration hack: Image.filter cannot be auto populated, so ignore + # images that have not been migrated to filters for now. + if image.filter: + image_data = { + 'name': image.name, + 'bundle_count': image.filter.get_test_runs(request.user).count(), + 'link': image.name, + } + images_data.append(image_data) images_data.sort(key=lambda d:d['name']) imageset_data = { 'name': imageset.name, @@ -1513,83 +1516,55 @@ @BreadCrumb("{name}", parent=image_report_list, needs=['name']) def image_report_detail(request, name): - filter = TestRunFilter.objects.get(name=name) - - # We are aiming to produce a table like this: - - # Build Number | 23 | ... | 40 | - # Date | YYYY-MM-DD | ... | YYYY-MM-DD | - # lava | 1/3 | ... | 4/5 | - # cts | 100/100 | ... | 88/100 | - # ... | ... | ... | ... | - # skia | 1/2 | ... | 3/3 | - - # Data processing proceeds in 3 steps: - - # 1) Get the bundles/builds. Image.get_latest_bundles() does the hard - # work here and then we just peel off the data we need from the bundles. - - # 2) Get all the test runs we are interested in, extract the data we - # need from them and associate them with the corresponding bundles. - - # 3) Organize the data so that it's natural for rendering the table - # (basically transposing it from being bundle -> testrun -> result to - # testrun -> bundle -> result). - - bundle_id_to_data = {} - - matches = filter.get_test_runs(request.user)[:50] + image = Image.objects.get(name=name) + matches = image.filter.get_test_runs(request.user, prefetch_related=['launchpad_bugs'])[:50] + + build_number_to_cols = {} + + test_run_names = set() for match in matches: - for tr in match.test_runs: - if tr.bundle_id not in bundle_id_to_data: - bundle = tr.bundle - bundle_id_to_data[bundle.id] = dict( - number=match.tag, - date=bundle.uploaded_on, - test_runs={}, - link=bundle.get_permalink(), - ) - - test_runs = TestRun.objects.filter( - bundle__id__in=list(bundle_id_to_data), - ).select_related( - 'bundle', 'denormalization', 'test').prefetch_related( - 'launchpad_bugs') - - test_run_names = set() - for test_run in test_runs: - name = test_run.test.test_id - denorm = test_run.denormalization - if denorm.count_pass == denorm.count_all(): - cls = 'present pass' - else: - cls = 'present fail' - bug_ids = sorted([b.bug_id for b in test_run.launchpad_bugs.all()]) - test_run_data = dict( - present=True, - cls=cls, - uuid=test_run.analyzer_assigned_uuid, - passes=denorm.count_pass, - total=denorm.count_all(), - link=test_run.get_permalink(), - bug_ids=bug_ids, - ) - bundle_id_to_data[test_run.bundle.id]['test_runs'][name] = test_run_data - if name != 'lava': - test_run_names.add(name) + for test_run in match.test_runs: + name = test_run.test.test_id + denorm = test_run.denormalization + if denorm.count_pass == denorm.count_all(): + cls = 'present pass' + else: + cls = 'present fail' + bug_ids = sorted([b.bug_id for b in test_run.launchpad_bugs.all()]) + test_run_data = dict( + present=True, + cls=cls, + uuid=test_run.analyzer_assigned_uuid, + passes=denorm.count_pass, + total=denorm.count_all(), + link=test_run.get_permalink(), + bug_ids=bug_ids, + ) + if match.tag not in build_number_to_cols: + # This assumes 1 bundle per match... + build_number_to_cols[match.tag] = { + 'test_runs': {}, + 'number': match.tag, + 'date': test_run.bundle.uploaded_on, + 'link': test_run.bundle.get_absolute_url(), + } + build_number_to_cols[match.tag]['test_runs'][name] = test_run_data + if name != 'lava': + test_run_names.add(name) + test_run_names = sorted(test_run_names) test_run_names.insert(0, 'lava') - bundles = sorted(bundle_id_to_data.values(), key=lambda d:d['number']) + cols = [c for n, c in sorted(build_number_to_cols.items())] table_data = [] for test_run_name in test_run_names: row_data = [] - for bundle in bundles: - test_run_data = bundle['test_runs'].get(test_run_name) + for col in cols: + test_run_data = col['test_runs'].get(test_run_name) if not test_run_data: test_run_data = dict( present=False, @@ -1601,9 +1576,9 @@ return render_to_response( "dashboard_app/image-report.html", { 'bread_crumb_trail': BreadCrumbTrail.leading_to( - image_report_detail, name=filter.name), - 'image': filter, - 'bundles': bundles, + image_report_detail, name=image.name), + 'image': image, + 'cols': cols, 'table_data': table_data, 'test_run_names': test_run_names, }, RequestContext(request))
- {{ bundle.number }} + {{ col.number }}
- {{ bundle.date|date }} + {{ col.date|date }}