=== modified file 'dashboard_app/admin.py'
@@ -36,6 +36,7 @@
ImageSet,
LaunchpadBug,
NamedAttribute,
+ PMQABundleStream,
SoftwarePackage,
SoftwareSource,
Tag,
@@ -95,7 +96,7 @@
the bundle stream itself.
"""
my_modeladmin = BundleAdmin(Bundle, modeladmin.admin_site)
- my_modeladmin.delete_selected_confirmation_template = 'admin/dashboard_app/cleanup_selected_bundle_confirmation.html'
+ my_modeladmin.delete_selected_confirmation_template = 'admin/dashboard_app/cleanup_selected_bundle_confirmation.html'
my_queryset = None
if request.POST.get('post'): # handle bundles
selected_bundles = request.POST.getlist('_selected_action')
@@ -106,7 +107,7 @@
my_queryset = bundle_stream.bundles.all()
else:
my_queryset = my_queryset | bundle_stream.bundles.all()
- return delete_selected(my_modeladmin, request, my_queryset)
+ return delete_selected(my_modeladmin, request, my_queryset)
cleanup_bundle_stream_selected.short_description = "Clean up selected %(verbose_name_plural)s"
@@ -215,6 +216,7 @@
admin.site.register(Image, ImageAdmin)
admin.site.register(ImageSet, ImageSetAdmin)
admin.site.register(LaunchpadBug, LaunchpadBugAdmin)
+admin.site.register(PMQABundleStream)
admin.site.register(SoftwarePackage, SoftwarePackageAdmin)
admin.site.register(SoftwareSource, SoftwareSourceAdmin)
admin.site.register(Test, TestAdmin)
=== modified file 'dashboard_app/filters.py'
@@ -111,7 +111,7 @@
pass_count = None # Only filled out for filters that dont specify a test
result_count = None # Ditto
- def serializable(self):
+ def serializable(self, include_links=True):
cases_by_test = {}
for test in self.filter_data['tests']:
# Not right if filter specifies a test more than once...
@@ -136,8 +136,9 @@
'skip': 0,
'unknown': 0,
'total': 0,
- 'link': url_prefix + tr.get_absolute_url(),
}
+ if include_links:
+ d['link'] = url_prefix + tr.get_absolute_url()
if tr.test in cases_by_test:
results = d['specific_results'] = []
for result in self.specific_results:
@@ -146,8 +147,9 @@
result_data = {
'test_case_id': result.test_case.test_case_id,
'result': result_str,
- 'link': url_prefix + result.get_absolute_url()
}
+ if include_links:
+ result_data['link'] = url_prefix + result.get_absolute_url()
if result.measurement is not None:
result_data['measurement'] = str(result.measurement)
if result.units is not None:
@@ -221,7 +223,7 @@
self.queryset = queryset
self.filter_data = filter_data
self.prefetch_related = prefetch_related
- if filter_data['build_number_attribute']:
+ if filter_data.get('build_number_attribute'):
self.key = 'build_number'
self.key_name = 'Build'
else:
@@ -372,9 +374,9 @@
).values('object_id')))
test_condition = None
- for test in filter_data['tests']:
+ for test in filter_data.get('tests', []):
case_ids = set()
- for test_case in test['test_cases']:
+ for test_case in test.get('test_cases', []):
case_ids.add(test_case.id)
if case_ids:
q = models.Q(
@@ -389,12 +391,12 @@
if test_condition:
conditions.append(test_condition)
- if filter_data['uploaded_by']:
+ if filter_data.get('uploaded_by'):
conditions.append(models.Q(bundle__uploaded_by=filter_data['uploaded_by']))
testruns = TestRun.objects.filter(*conditions)
- if filter_data['build_number_attribute']:
+ if filter_data.get('build_number_attribute'):
if descending:
ob = ['-build_number']
else:
=== added file 'dashboard_app/migrations/0026_auto__add_pmqabundlestream.py'
@@ -0,0 +1,277 @@
+# -*- 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 'PMQABundleStream'
+ db.create_table('dashboard_app_pmqabundlestream', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('bundle_stream', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['dashboard_app.BundleStream'])),
+ ))
+ db.send_create_signal('dashboard_app', ['PMQABundleStream'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'PMQABundleStream'
+ db.delete_table('dashboard_app_pmqabundlestream')
+
+
+ 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.pmqabundlestream': {
+ 'Meta': {'object_name': 'PMQABundleStream'},
+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.BundleStream']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ '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'}),
+ 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': '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'
@@ -1772,3 +1772,8 @@
bundle_was_deserialized.connect(send_bundle_notifications)
+
+
+class PMQABundleStream(models.Model):
+
+ bundle_stream = models.ForeignKey(BundleStream, related_name='+')
=== added file 'dashboard_app/templates/dashboard_app/pmqa-view.html'
@@ -0,0 +1,87 @@
+{% extends "dashboard_app/_content.html" %}
+
+{% block extrahead %}
+{{ block.super }}
+<style type="text/css">
+table {
+ border-collapse: collapse;
+}
+td, th {
+ min-width: 25ex;
+ padding: 3px 4px;
+ border: thin solid black;
+}
+td.pass {
+ background-color: #4e4;
+}
+td.fail {
+ background-color: #e44;
+}
+td.missing {
+ background-color: #bbb;
+}
+
+</style>
+{% endblock %}
+
+{% block content %}
+<h1>PMQA view</h1>
+
+<table>
+ <thead>
+ <tr>
+ <th>
+ </th>
+ {% for device_type in device_types_with_results %}
+ {% if device_type.width %}
+ <th colspan="{{ device_type.width }}">
+ {{ device_type.sn }}
+ </th>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ <tr>
+ <th>
+ </th>
+ {% for device_type in device_types_with_results %}
+ <th>
+ <a href="{{ device_type.filter_link }}">{{ device_type.device_type }}</a>
+ </th>
+ {% endfor %}
+ </tr>
+ <tr>
+ <th>
+ Test prefix
+ </th>
+ {% for device_type in device_types_with_results %}
+ <th>
+ <a href="{{ device_type.link }}">{{ device_type.date }} (build {{device_type.build}})</a>
+ <br />
+ {% if device_type.last_difference %}
+ A different result was last seen in build <a href="{{ device_type.last_difference.1 }}">{{ device_type.last_difference.0 }}</a>
+ {% else %}
+ No different result has been seen
+ {% endif %}
+ </th>
+ {% endfor %}
+ </tr>
+ </thead>
+ <tbody>
+ {% for prefix, board_results in results %}
+ <tr>
+ <td>
+ {{ prefix }}
+ </td>
+ {% for board_result in board_results %}
+ <td class="{{ board_result.css_class }}">
+ {% if board_result.present %}
+ {{ board_result.pass }} / {{ board_result.total }}
+ {% endif %}
+ </td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+
+{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/pmqa_filter.html'
@@ -0,0 +1,29 @@
+{% extends "dashboard_app/_content.html" %}
+{% load i18n %}
+{% load django_tables2 %}
+
+{% block extrahead %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/filter-detail.css"/>
+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/filter-detail.js"></script>
+{% endblock %}
+
+{% block content %}
+
+<h1>PMQA results for {{ bundle_stream }} on {{ device_type }}</h1>
+{% render_table filter_table %}
+
+<p>
+ <button id="compare-button">Compare builds</button>
+ <span id="first-prompt" style="display:none">
+ Click a build to compare.
+ </span>
+ <span id="second-prompt" style="display:none">
+ Click build to compare with build <span id="p2-build">XXX</span>.
+ </span>
+ <span id="third-prompt" style="display:none">
+ Click <a href="#">here</a> to compare with build <span id="p3-build-1">XXX</span> with build <span id="p3-build-2">XXX</span>.
+ </span>
+</p>
+
+{% endblock %}
=== modified file 'dashboard_app/urls.py'
@@ -82,6 +82,10 @@
url(r'^efforts/(?P<pk>[0-9]+)/update/$', 'testing_effort_update'),
url(r'^efforts/(?P<project_identifier>[a-z0-9-]+)/\+new/$', 'testing_effort_create'),
url(r'^image-reports/$', 'images.image_report_list'),
+ url(r'^pmqa$', 'pmqa.pmqa_view'),
+ url(r'^pmqa(?P<pathname>/[a-zA-Z0-9/._-]+/)(?P<device_type>[a-zA-Z0-9-_]+)$', 'pmqa.pmqa_filter_view'),
+ url(r'^pmqa(?P<pathname>/[a-zA-Z0-9/._-]+/)(?P<device_type>[a-zA-Z0-9-_]+)/json$', 'pmqa.pmqa_filter_view_json'),
+ url(r'^pmqa(?P<pathname>/[a-zA-Z0-9/._-]+/)(?P<device_type>[a-zA-Z0-9-_]+)/\+compare/(?P<build1>[0-9]+)/(?P<build2>[0-9]+)$', 'pmqa.compare_pmqa_results'),
url(r'^image-reports/(?P<name>[A-Za-z0-9_-]+)$', 'images.image_report_detail'),
url(r'^api/link-bug-to-testrun', 'images.link_bug_to_testrun'),
url(r'^api/unlink-bug-and-testrun', 'images.unlink_bug_and_testrun'),
=== modified file 'dashboard_app/views/filters/tables.py'
@@ -216,8 +216,8 @@
passes = Column(accessor='pass_count')
total = Column(accessor='result_count')
- def get_queryset(self, user, filter):
- return evaluate_filter(user, filter.as_data())
+ def get_queryset(self, user, filter_data):
+ return evaluate_filter(user, filter_data)
datatable_opts = {
"sPaginationType": "full_numbers",
@@ -227,9 +227,6 @@
class FilterPreviewTable(FilterTable):
- def get_queryset(self, user, form):
- return evaluate_filter(user, form.as_data())
-
datatable_opts = FilterTable.datatable_opts.copy()
datatable_opts.update({
"iDisplayLength": 10,
=== modified file 'dashboard_app/views/filters/views.py'
@@ -83,7 +83,7 @@
def filter_json(request, username, name):
filter = TestRunFilter.objects.get(owner__username=username, name=name)
- return FilterTable.json(request, params=(request.user, filter))
+ return FilterTable.json(request, params=(request.user, filter.as_data()))
@@ -95,7 +95,7 @@
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))
+ return FilterPreviewTable.json(request, params=(request.user, form.as_data()))
@BreadCrumb("Filter ~{username}/{name}", parent=filters_list, needs=['username', 'name'])
@@ -118,7 +118,7 @@
'filter_table': FilterTable(
"filter-table",
reverse(filter_json, kwargs=dict(username=username, name=name)),
- params=(request.user, filter)),
+ params=(request.user, filter.as_data())),
'bread_crumb_trail': BreadCrumbTrail.leading_to(
filter_detail, name=name, username=username),
}, RequestContext(request)
@@ -177,7 +177,7 @@
'table': FilterPreviewTable(
'filter-preview',
reverse(filter_preview_json) + '?' + c.urlencode(),
- params=(request.user, form)),
+ params=(request.user, form.as_data())),
}, RequestContext(request))
else:
form = TestRunFilterForm(request.user, instance=instance)
@@ -323,16 +323,8 @@
return differences
-@BreadCrumb(
- "Comparing builds {tag1} and {tag2}",
- parent=filter_detail,
- needs=['username', 'name', 'tag1', 'tag2'])
-def compare_matches(request, username, name, tag1, tag2):
- filter = TestRunFilter.objects.get(owner__username=username, name=name)
- if not filter.public and filter.owner != request.user:
- raise PermissionDenied()
- filter_data = filter.as_data()
- matches = evaluate_filter(request.user, filter_data)
+def compare_filter_matches(user, filter_data, tag1, tag2):
+ matches = evaluate_filter(user, filter_data)
match1, match2 = matches.with_tags(tag1, tag2)
test_cases_for_test_id = {}
for test in filter_data['tests']:
@@ -388,6 +380,19 @@
tr=tr,
tag=tag,
cases=cases))
+ return test_run_info
+
+
+@BreadCrumb(
+ "Comparing builds {tag1} and {tag2}",
+ parent=filter_detail,
+ needs=['username', 'name', 'tag1', 'tag2'])
+def compare_matches(request, username, name, tag1, tag2):
+ filter = TestRunFilter.objects.get(owner__username=username, name=name)
+ if not filter.public and filter.owner != request.user:
+ raise PermissionDenied()
+ filter_data = filter.as_data()
+ test_run_info = compare_filter_matches(request.user, filter_data, tag1, tag2)
return render_to_response(
"dashboard_app/filter_compare_matches.html", {
'test_run_info': test_run_info,
=== added file 'dashboard_app/views/pmqa.py'
@@ -0,0 +1,203 @@
+# Copyright (C) 2010-2012 Linaro Limited
+#
+# Author: Michael Hudson-Doyle <michael.hudson@linaro.org>
+#
+# This file is part of Launch Control.
+#
+# Launch Control is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# Launch Control is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
+
+
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from lava_server.bread_crumbs import (
+ BreadCrumb,
+ BreadCrumbTrail,
+)
+
+from dashboard_app.filters import evaluate_filter
+from dashboard_app.models import (
+ BundleStream,
+ PMQABundleStream,
+ Test,
+)
+from dashboard_app.views import index
+from dashboard_app.views.filters.tables import (
+ FilterTable,
+ )
+from dashboard_app.views.filters.views import (
+ compare_filter_matches,
+ )
+
+
+@BreadCrumb("PM QA view", parent=index)
+@login_required
+def pmqa_view(request):
+ test = Test.objects.get(test_id='pwrmgmt')
+ device_types_with_results = []
+ prefix__device_type_result = {}
+
+ from lava_scheduler_app.models import DeviceType
+ device_types = list(DeviceType.objects.filter(display=True).values_list('name', flat=True))
+ bundle_streams = [pmqabs.bundle_stream for pmqabs in PMQABundleStream.objects.all()]
+ bundle_streams.sort(key=lambda bs:bs.pathname)
+
+ for bs in bundle_streams:
+ c = len(device_types_with_results)
+ for device_type in device_types:
+ if device_type.startswith('rtsm'):
+ continue
+ filter_data = {
+ 'bundle_streams': [bs],
+ 'attributes': [('target.device_type', device_type)],
+ 'tests': [{'test':test, 'test_cases':[]}],
+ 'build_number_attribute': 'build.id',
+ }
+ matches = list(evaluate_filter(request.user, filter_data)[:50])
+ if matches:
+ match = matches[0]
+ m0 = match.serializable(include_links=False)
+ del m0['tag']
+ last_difference = None
+ for m in matches[1:]:
+ m1 = m.serializable(include_links=False)
+ del m1['tag']
+ if m1 != m0:
+ last_difference = (
+ m.tag,
+ reverse(compare_pmqa_results,
+ kwargs={
+ 'pathname': bs.pathname,
+ 'device_type': device_type,
+ 'build1': str(m.tag),
+ 'build2': str(match.tag),
+ }))
+ break
+ tr = match.test_runs[0]
+ device_types_with_results.append({
+ 'sn': bs.slug,
+ 'device_type': device_type,
+ 'date': tr.bundle.uploaded_on,
+ 'build': match.tag,
+ 'link': tr.get_absolute_url(),
+ 'width': 0,
+ 'last_difference': last_difference,
+ 'filter_link': reverse(pmqa_filter_view, kwargs=dict(
+ pathname=bs.pathname, device_type=device_type)),
+ })
+ for result in tr.test_results.all().select_related('test_case'):
+ prefix = result.test_case.test_case_id.split('.')[0]
+ device_type__result = prefix__device_type_result.setdefault(prefix, {})
+ d = device_type__result.setdefault(device_type, {'pass': 0, 'total': 0, 'present':True})
+ if result.result == result.RESULT_PASS:
+ d['pass'] += 1
+ d['total'] += 1
+ if len(device_types_with_results) > c:
+ device_types_with_results[c]['width'] = len(device_types_with_results) - c
+ results = []
+ prefixes = sorted(prefix__device_type_result)
+ for prefix in prefixes:
+ board_results = []
+ for d in device_types_with_results:
+ cell_data = prefix__device_type_result[prefix].get(d['device_type'])
+ if cell_data is not None:
+ if cell_data['total'] == cell_data['pass']:
+ cell_data['css_class'] = 'pass'
+ else:
+ cell_data['css_class'] = 'fail'
+ else:
+ cell_data = {
+ 'css_class': 'missing',
+ 'present': False,
+ }
+ board_results.append(cell_data)
+ results.append((prefix, board_results))
+ return render_to_response(
+ "dashboard_app/pmqa-view.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(pmqa_view),
+ 'device_types_with_results': device_types_with_results,
+ 'results': results,
+ }, RequestContext(request))
+
+
+def pmqa_filter_view_json(request, pathname, device_type):
+ test = Test.objects.get(test_id='pwrmgmt')
+ bs = BundleStream.objects.get(pathname=pathname)
+ filter_data = {
+ 'bundle_streams': [bs],
+ 'attributes': [('target.device_type', device_type)],
+ 'tests': [{'test':test, 'test_cases':[]}],
+ 'build_number_attribute': 'build.id',
+ }
+ return FilterTable.json(request, params=(request.user, filter_data))
+
+
+@BreadCrumb(
+ "PMQA results for {pathname} on {device_type}",
+ parent=pmqa_view,
+ needs=['pathname', 'device_type'])
+def pmqa_filter_view(request, pathname, device_type):
+ test = Test.objects.get(test_id='pwrmgmt')
+ bs = BundleStream.objects.get(pathname=pathname)
+ filter_data = {
+ 'bundle_streams': [bs],
+ 'attributes': [('target.device_type', device_type)],
+ 'tests': [{'test':test, 'test_cases':[]}],
+ 'build_number_attribute': 'build.id',
+ }
+ return render_to_response(
+ "dashboard_app/pmqa_filter.html", {
+ 'filter_table': FilterTable(
+ "filter-table",
+ reverse(
+ pmqa_filter_view_json,
+ kwargs=dict(
+ pathname=pathname,
+ device_type=device_type)),
+ params=(request.user, filter_data)),
+ 'bundle_stream': bs.slug,
+ 'device_type': device_type,
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ pmqa_filter_view,
+ pathname=pathname,
+ device_type=device_type),
+ }, RequestContext(request))
+
+
+@BreadCrumb(
+ "Comparing builds {build1} and {build2}",
+ parent=pmqa_filter_view,
+ needs=['pathname', 'device_type', 'build1', 'build2'])
+def compare_pmqa_results(request, pathname, device_type, build1, build2):
+ test = Test.objects.get(test_id='pwrmgmt')
+ bs = BundleStream.objects.get(pathname=pathname)
+ filter_data = {
+ 'bundle_streams': [bs],
+ 'attributes': [('target.device_type', device_type)],
+ 'tests': [{'test':test, 'test_cases':[]}],
+ 'build_number_attribute': 'build.id',
+ }
+ test_run_info = compare_filter_matches(request.user, filter_data, build1, build2)
+ return render_to_response(
+ "dashboard_app/filter_compare_matches.html", {
+ 'test_run_info': test_run_info,
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ compare_pmqa_results,
+ pathname=pathname,
+ device_type=device_type,
+ build1=build1,
+ build2=build2),
+ }, RequestContext(request))
+