=== modified file 'dashboard_app/__init__.py'
@@ -20,4 +20,4 @@
Dashboard Application (package)
"""
-__version__ = (0, 7, 2, "final", 0)
+__version__ = (0, 8, 0, "final", 0)
=== modified file 'dashboard_app/admin.py'
@@ -26,19 +26,21 @@
from django.utils.translation import ugettext as _
from dashboard_app.models import (
- Attachment,
- Bundle,
- BundleDeserializationError,
- BundleStream,
- HardwareDevice,
- NamedAttribute,
- SoftwarePackage,
- SoftwareSource,
- Test,
- TestCase,
- TestResult,
- TestRun,
- )
+ Attachment,
+ Bundle,
+ BundleDeserializationError,
+ BundleStream,
+ HardwareDevice,
+ NamedAttribute,
+ SoftwarePackage,
+ SoftwareSource,
+ Tag,
+ Test,
+ TestCase,
+ TestResult,
+ TestRun,
+ TestingEffort,
+)
class BundleAdmin(admin.ModelAdmin):
@@ -147,6 +149,10 @@
inlines = [NamedAttributeInline]
+class TestingEffortAdmin(admin.ModelAdmin):
+ list_display = ('__unicode__', 'project')
+
+
admin.site.register(Attachment)
admin.site.register(Bundle, BundleAdmin)
admin.site.register(BundleDeserializationError, BundleDeserializationErrorAdmin)
@@ -158,3 +164,5 @@
admin.site.register(TestCase, TestCaseAdmin)
admin.site.register(TestResult, TestResultAdmin)
admin.site.register(TestRun, TestRunAdmin)
+admin.site.register(Tag)
+admin.site.register(TestingEffort, TestingEffortAdmin)
=== modified file 'dashboard_app/helpers.py'
@@ -100,7 +100,7 @@
Note: This function uses commit_on_success to ensure the database is in
a consistent state after IntegrityErrors that would clog the
transaction on pgsql. Since transactions will not rollback any files we
- created in the meantime there is is a helper that cleans attachments in
+ created in the meantime there is a helper that cleans attachments in
case something goes wrong
"""
self._import_document(s_bundle, doc)
@@ -161,6 +161,7 @@
self._log('attributes')
# collect all the changes that happen before the previous save
s_test_run.save()
+ s_test_run.denormalize()
return s_test_run
def _import_software_context(self, c_test_run, s_test_run):
@@ -681,6 +682,24 @@
ContentFile(content))
+class BundleFormatImporter_1_3(BundleFormatImporter_1_2):
+ """
+ IFormatImporter subclass capable of loading "Dashboard Bundle Format 1.3"
+ """
+
+ def _import_test_run(self, c_test_run, s_bundle):
+ from dashboard_app.models import Tag
+
+ s_test_run = super(BundleFormatImporter_1_3, self)._import_test_run(c_test_run, s_bundle)
+ self._log('tags')
+ for c_tag in c_test_run.get("tags", []):
+ s_tag, created = Tag.objects.get_or_create(name=c_tag)
+ if created:
+ s_tag.save()
+ s_test_run.tags.add(s_tag)
+ return s_test_run
+
+
class BundleDeserializer(object):
"""
Helper class for de-serializing JSON bundle content into database models
@@ -691,6 +710,7 @@
"Dashboard Bundle Format 1.0.1": BundleFormatImporter_1_0_1,
"Dashboard Bundle Format 1.1": BundleFormatImporter_1_1,
"Dashboard Bundle Format 1.2": BundleFormatImporter_1_2,
+ "Dashboard Bundle Format 1.3": BundleFormatImporter_1_3,
}
def deserialize(self, s_bundle, prefer_evolution):
=== modified file 'dashboard_app/managers.py'
@@ -51,3 +51,20 @@
raise
else:
return bundle
+
+
+class TestRunDenormalizationManager(models.Manager):
+
+ def create_from_test_run(self, test_run):
+ from dashboard_app.models import TestResult
+ stats = test_run.test_results.values('result').annotate(
+ count=models.Count('result')).order_by()
+ result = dict([
+ (TestResult.RESULT_MAP[item['result']], item['count'])
+ for item in stats])
+ return self.create(
+ test_run=test_run,
+ count_pass=result.get('pass', 0),
+ count_fail=result.get('fail', 0),
+ count_skip=result.get('skip', 0),
+ count_unknown=result.get('unknown', 0))
=== added file 'dashboard_app/migrations/0006_auto__chg_field_bundledeserializationerror_bundle.py'
@@ -0,0 +1,176 @@
+# encoding: 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):
+
+ # Changing field 'BundleDeserializationError.bundle'
+ db.alter_column('dashboard_app_bundledeserializationerror', 'bundle_id', self.gf('django.db.models.fields.related.OneToOneField')(unique=True, primary_key=True, to=orm['dashboard_app.Bundle']))
+
+
+ def backwards(self, orm):
+
+ # Changing field 'BundleDeserializationError.bundle'
+ db.alter_column('dashboard_app_bundledeserializationerror', 'bundle_id', self.gf('django.db.models.fields.related.ForeignKey')(unique=True, primary_key=True, to=orm['dashboard_app.Bundle']))
+
+
+ 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'},
+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ '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.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.CharField', [], {'max_length': '32'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '512'})
+ },
+ '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.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.CharField', [], {'max_length': '100', '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.CharField', [], {'max_length': '100'}),
+ 'units': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+ },
+ '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'}),
+ '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'})
+ }
+ }
+
+ complete_apps = ['dashboard_app']
=== added file 'dashboard_app/migrations/0007_auto__add_tag.py'
@@ -0,0 +1,197 @@
+# encoding: 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 'Tag'
+ db.create_table('dashboard_app_tag', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=256, db_index=True)),
+ ))
+ db.send_create_signal('dashboard_app', ['Tag'])
+
+ # Adding M2M table for field tags on 'TestRun'
+ db.create_table('dashboard_app_testrun_tags', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('testrun', models.ForeignKey(orm['dashboard_app.testrun'], null=False)),
+ ('tag', models.ForeignKey(orm['dashboard_app.tag'], null=False))
+ ))
+ db.create_unique('dashboard_app_testrun_tags', ['testrun_id', 'tag_id'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Tag'
+ db.delete_table('dashboard_app_tag')
+
+ # Removing M2M table for field tags on 'TestRun'
+ db.delete_table('dashboard_app_testrun_tags')
+
+
+ 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'},
+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ '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.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.CharField', [], {'max_length': '32'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '512'})
+ },
+ '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', 'db_index': 'True'})
+ },
+ '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.CharField', [], {'max_length': '100', '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.CharField', [], {'max_length': '100'}),
+ 'units': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+ },
+ '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'})
+ }
+ }
+
+ complete_apps = ['dashboard_app']
=== added file 'dashboard_app/migrations/0008_auto__add_testingeffort.py'
@@ -0,0 +1,220 @@
+# encoding: 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 'TestingEffort'
+ db.create_table('dashboard_app_testingeffort', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='testing_efforts', to=orm['lava_projects.Project'])),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
+ ('description', self.gf('django.db.models.fields.TextField')()),
+ ))
+ db.send_create_signal('dashboard_app', ['TestingEffort'])
+
+ # Adding M2M table for field tags on 'TestingEffort'
+ db.create_table('dashboard_app_testingeffort_tags', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('testingeffort', models.ForeignKey(orm['dashboard_app.testingeffort'], null=False)),
+ ('tag', models.ForeignKey(orm['dashboard_app.tag'], null=False))
+ ))
+ db.create_unique('dashboard_app_testingeffort_tags', ['testingeffort_id', 'tag_id'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'TestingEffort'
+ db.delete_table('dashboard_app_testingeffort')
+
+ # Removing M2M table for field tags on 'TestingEffort'
+ db.delete_table('dashboard_app_testingeffort_tags')
+
+
+ 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'},
+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ '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.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.CharField', [], {'max_length': '32'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '512'})
+ },
+ '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', 'db_index': 'True'})
+ },
+ '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.CharField', [], {'max_length': '100', '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.CharField', [], {'max_length': '100'}),
+ 'units': ('django.db.models.fields.CharField', [], {'max_length': '100', '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'})
+ },
+ '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', 'db_index': 'True'}),
+ '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']
=== added file 'dashboard_app/migrations/0009_auto__add_testrundenormalization.py'
@@ -0,0 +1,218 @@
+# encoding: 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 'TestRunDenormalization'
+ db.create_table('dashboard_app_testrundenormalization', (
+ ('test_run', self.gf('django.db.models.fields.related.OneToOneField')(related_name='denormalization', unique=True, primary_key=True, to=orm['dashboard_app.TestRun'])),
+ ('count_pass', self.gf('django.db.models.fields.PositiveIntegerField')()),
+ ('count_fail', self.gf('django.db.models.fields.PositiveIntegerField')()),
+ ('count_skip', self.gf('django.db.models.fields.PositiveIntegerField')()),
+ ('count_unknown', self.gf('django.db.models.fields.PositiveIntegerField')()),
+ ))
+ db.send_create_signal('dashboard_app', ['TestRunDenormalization'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'TestRunDenormalization'
+ db.delete_table('dashboard_app_testrundenormalization')
+
+
+ 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'},
+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ '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.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.CharField', [], {'max_length': '32'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '512'})
+ },
+ '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', 'db_index': 'True'})
+ },
+ '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.CharField', [], {'max_length': '100', '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.CharField', [], {'max_length': '100'}),
+ 'units': ('django.db.models.fields.CharField', [], {'max_length': '100', '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']"})
+ },
+ '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', 'db_index': 'True'}),
+ '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']
=== added file 'dashboard_app/migrations/0010_denormalize_test_run.py'
@@ -0,0 +1,235 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ # Result codes
+ RESULT_PASS = 0
+ RESULT_FAIL = 1
+ RESULT_SKIP = 2
+ RESULT_UNKNOWN = 3
+ # Code-to-name mapping
+ RESULT_MAP = {
+ RESULT_PASS: 'pass',
+ RESULT_FAIL: 'fail',
+ RESULT_SKIP: 'skip',
+ RESULT_UNKNOWN: 'unknown'
+ }
+ for test_run in orm.TestRun.objects.all():
+ stats = test_run.test_results.order_by(
+ # Disable sorting
+ ).values(
+ 'result' # only get the result outcome (pass/fail/etc)
+ ).annotate(
+ count=models.Count('result') # Count number of items with this value
+ )
+ # Translate the 0-4 element array into a 0-4 element dictionary
+ result = dict([
+ (RESULT_MAP[item['result']], item['count'])
+ for item in stats])
+ # Create a denormalized test run instance
+ orm.TestRunDenormalization.objects.create(
+ test_run=test_run,
+ count_pass=result.get('pass', 0),
+ count_fail=result.get('fail', 0),
+ count_skip=result.get('skip', 0),
+ count_unknown=result.get('unknown', 0))
+
+ def backwards(self, orm):
+ orm.TestRunDenormalization.objects.all().delete()
+
+ 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'},
+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ '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.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.CharField', [], {'max_length': '32'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '512'})
+ },
+ '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', 'db_index': 'True'})
+ },
+ '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.CharField', [], {'max_length': '100', '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.CharField', [], {'max_length': '100'}),
+ 'units': ('django.db.models.fields.CharField', [], {'max_length': '100', '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']"})
+ },
+ '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', 'db_index': 'True'}),
+ 'is_aggregate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}),
+ 'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['dashboard_app']
=== modified file 'dashboard_app/models.py'
@@ -39,12 +39,16 @@
from django.utils.translation import ungettext
from django_restricted_resource.models import RestrictedResource
+from lava_projects.models import Project
+from linaro_dashboard_bundle.io import DocumentIO
from dashboard_app.helpers import BundleDeserializer
-from dashboard_app.managers import BundleManager
+from dashboard_app.managers import BundleManager, TestRunDenormalizationManager
from dashboard_app.repositories import RepositoryItem
from dashboard_app.repositories.data_report import DataReportRepository
from dashboard_app.repositories.data_view import DataViewRepository
+from dashboard_app.signals import bundle_was_deserialized
+
# Fix some django issues we ran into
from dashboard_app.patches import patch
@@ -397,6 +401,7 @@
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]
@@ -404,8 +409,10 @@
import_error.traceback = traceback.format_exc()
import_error.save()
else:
- if self.deserialization_error.count():
- self.deserialization_error.get().delete()
+ try:
+ self.deserialization_error.delete()
+ except BundleDeserializationError.DoesNotExist:
+ pass
self.is_deserialized = True
self.save()
@@ -446,6 +453,17 @@
finally:
self.content.close()
+ def get_document_format(self):
+ self.content.open('rb')
+ try:
+ fmt, doc = DocumentIO.load(self.content)
+ return fmt
+ finally:
+ self.content.close()
+
+ def get_serialization_format(self):
+ return "JSON"
+
class SanitizedBundle(object):
@@ -485,7 +503,7 @@
The relevant logic for managing this is in the Bundle.deserialize()
"""
- bundle = models.ForeignKey(
+ bundle = models.OneToOneField(
Bundle,
primary_key = True,
unique = True,
@@ -740,6 +758,14 @@
attributes = generic.GenericRelation(NamedAttribute)
+ # Tags
+
+ tags = models.ManyToManyField(
+ "Tag",
+ blank=True,
+ related_name='test_runs',
+ verbose_name=_(u"Tags"))
+
# Attachments
attachments = generic.GenericRelation('Attachment')
@@ -757,6 +783,34 @@
def get_permalink(self):
return reverse("dashboard_app.views.redirect_to_test_run", args=[self.analyzer_assigned_uuid])
+ def get_board(self):
+ """
+ Return an associated Board device, if any.
+ """
+ try:
+ return self.devices.filter(device_type="device.board").get()
+ except HardwareDevice.DoesNotExist:
+ pass
+ except HardwareDevice.MultipleObjectsReturned:
+ pass
+
+ def get_results(self):
+ """
+ Get all results efficiently
+ """
+ return self.test_results.select_related(
+ "test_case", # explicit join on test_case which might be NULL
+ "test_run", # explicit join on test run, needed by all the get_absolute_url() methods
+ "test_run__bundle", # explicit join on bundle
+ "test_run__bundle__bundle_stream", # explicit join on bundle stream
+ ).order_by("relative_index") # sort as they showed up in the bundle
+
+ def denormalize(self):
+ try:
+ self.denormalization
+ except TestRunDenormalization.DoesNotExist:
+ TestRunDenormalization.objects.create_from_test_run(self)
+
def _get_summary_results(self, factor=3):
stats = self.test_results.values('result').annotate(
count=models.Count('result')).order_by()
@@ -776,6 +830,39 @@
ordering = ['-import_assigned_date']
+class TestRunDenormalization(models.Model):
+ """
+ Denormalized model for test run
+ """
+
+ test_run = models.OneToOneField(
+ TestRun,
+ primary_key=True,
+ related_name="denormalization")
+
+ count_pass = models.PositiveIntegerField(
+ null=False,
+ blank=False)
+
+ count_fail = models.PositiveIntegerField(
+ null=False,
+ blank=False)
+
+ count_skip = models.PositiveIntegerField(
+ null=False,
+ blank=False)
+
+ count_unknown = models.PositiveIntegerField(
+ null=False,
+ blank=False)
+
+ def count_all(self):
+ return (self.count_pass + self.count_fail + self.count_skip +
+ self.count_unknown)
+
+ objects = TestRunDenormalizationManager()
+
+
class Attachment(models.Model):
"""
Model for adding attachments to any other models.
@@ -1302,3 +1389,53 @@
for attr in NamedAttribute.objects.filter(
name='hwpack.type').values('value').distinct()]
return hwpack_list
+
+
+class Tag(models.Model):
+ """
+ Tag used for marking test runs.
+ """
+ name = models.SlugField(
+ verbose_name=_(u"Tag"),
+ max_length=256,
+ db_index=True,
+ unique=True)
+
+ def __unicode__(self):
+ return self.name
+
+
+class TestingEffort(models.Model):
+ """
+ A collaborative effort to test something.
+
+ Uses tags to associate with test runs.
+ """
+ project = models.ForeignKey(
+ Project,
+ related_name="testing_efforts")
+
+ name = models.CharField(
+ verbose_name=_(u"Name"),
+ max_length=100)
+
+ description = models.TextField(
+ verbose_name=_(u"Description"),
+ help_text=_(u"Description of this testing effort"))
+
+ tags = models.ManyToManyField(
+ Tag,
+ verbose_name=_(u"Tags"),
+ related_name="testing_efforts")
+
+ def __unicode__(self):
+ return self.name
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ("dashboard_app.views.testing_effort_detail", [self.pk])
+
+ def get_test_runs(self):
+ return TestRun.objects.order_by(
+ ).filter(
+ tags__in=self.tags.all())
=== added file 'dashboard_app/signals.py'
@@ -0,0 +1,21 @@
+# Copyright (C) 2010, 2011 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@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.dispatch import Signal
+
+bundle_was_deserialized = Signal(providing_args=['bundle'])
=== modified file 'dashboard_app/templates/dashboard_app/_extension_navigation.html'
@@ -5,6 +5,8 @@
>{% trans "Back to LAVA" %}</a></li>
<li><a href="{% url dashboard_app.views.image_status_list %}"
>{% trans "Image Status" %}</a></li>
+ <li><a href="{% url dashboard_app.views.testing_effort_list %}"
+ >{% trans "Testing efforts" %}</a></li>
<li><a href="{% url dashboard_app.views.bundle_stream_list %}"
>{% trans "Bundle Streams" %}</a></li>
<li><a href="{% url dashboard_app.views.test_list %}"
=== modified file 'dashboard_app/templates/dashboard_app/_test_run_list_table.html'
@@ -6,10 +6,6 @@
<th>{% trans "Test" %}</th>
<th>{% trans "Uploaded On" %} </th>
<th>{% trans "Analyzed On" %}</th>
- <th>{% trans "Pass" %}</th>
- <th>{% trans "Fail" %}</th>
- <th>{% trans "Skip" %}</th>
- <th>{% trans "Unknown" %}</th>
</tr>
</thead>
<tbody>
@@ -19,12 +15,6 @@
<td><a href="{{ test_run.test.get_absolute_url }}">{{ test_run.test }}</a></td>
<td>{{ test_run.bundle.uploaded_on|date:"Y-m-d H:i:s" }}</td>
<td>{{ test_run.analyzer_assigned_date|date:"Y-m-d H:i:s" }}</td>
- {% with test_run.get_summary_results as summary %}
- <td>{{ summary.pass|default:0 }}</td>
- <td>{{ summary.fail|default:0 }}</td>
- <td>{{ summary.skip|default:0 }}</td>
- <td>{{ summary.unknown|default:0 }}</td>
- {% endwith %}
</tr>
{% endfor %}
</tbody>
=== modified file 'dashboard_app/templates/dashboard_app/attachment_list.html'
@@ -17,11 +17,17 @@
<tr>
<td><a href="{{ attachment.get_absolute_url }}"
><code>{{ attachment.content_filename }}</code></a></td>
- <td>{{ attachment.content.size|filesizeformat }}</td>
+ <td>
+ {% if attachment.content %}
+ {{ attachment.content.size|filesizeformat }}
+ {% else %}
+ Not available
+ {% endif %}
+ </td>
<td><code>{{ attachment.mime_type }}</code></td>
<td>
{% if attachment.public_url %}
- <a href="{{ attachment.public_url }}">public url</a>
+ <a href="{{ attachment.public_url }}">public URL</a>
{% endif %}
</td>
</tr>
=== modified file 'dashboard_app/templates/dashboard_app/bundle_detail.html'
@@ -1,4 +1,4 @@
-{% extends "dashboard_app/_content.html" %}
+{% extends "dashboard_app/_content_with_sidebar.html" %}
{% load humanize %}
{% load i18n %}
{% load stylize %}
@@ -10,21 +10,48 @@
{% endblock %}
+
+{% block sidebar %}
+<h3>Permalink</h3>
+<p>You can navigate to this bundle, regardless of the bundle stream it is
+located in, by using this <a href="{{bundle.get_permalink}}">permalink</a></p>
+
+<h3>Upload details</h3>
+{% if bundle.uploaded_by %}
+<p>This bundle was uploaded by <strong>{{bundle.uploaded_by}}</strong> on
+{{bundle.uploaded_on}} ({{bundle.uploaded_on|timesince}} ago)</p>
+{% else %}
+<p>This bundle was uploaded by an anonymous contributor on
+{{bundle.uploaded_on}} ({{bundle.uploaded_on|timesince}} ago)</p>
+{% endif %}
+
+<h3>File details</h3>
+<dl>
+ <dt>Declared file name:</dt>
+ <dd><q>{{ bundle.content_filename }}</q></dd>
+ <dt>Content SHA1:</dt>
+ <dd>{{ bundle.content_sha1 }}</dd>
+ <dt>Content size:</dt>
+ <dd>{{ bundle.content.size|filesizeformat }}</dd>
+</dl>
+
+<h3>Storage and format</h3>
+<dl>
+ <dt>Document format:</dt>
+ <dd><q>{{bundle.get_document_format}}</q></dd>
+ <dt>Serialization format:</dt>
+ <dd><q>{{ bundle.get_serialization_format}}</q></dd>
+</dl>
+
+<h3>Tips</h3>
+<p>You can download this bundle with the following command:</p>
+<div class="console">
+ <code>lava-dashboard-tool --dashboard-url=http://{{site.domain}}{% url lava.api_handler %} get {{bundle.content_sha1}}</code>
+</div>
+{% endblock %}
+
+
{% block content %}
-<div class="ui-widget">
- <div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0.7em">
- <span
- class="ui-icon ui-icon-info"
- style="float: left; margin-right: 0.3em;"></span>
- <strong>{% trans "Note:" %}</strong>
- {% blocktrans %}
- You can navigate to this bundle, regardless of the bundle stream it is
- located in, by using this
- {% endblocktrans %}
- <a href="{{ bundle.get_permalink }}" >{% trans "permalink" %}</a>
- </div>
-</div>
-<br/>
<script type="text/javascript">
$(document).ready(function() {
$("#tabs").tabs({
@@ -55,7 +82,7 @@
{% if bundle.is_deserialized %}
<li><a href="#tab-test-runs">{% trans "Test Runs" %}</a></li>
{% endif %}
- {% if bundle.deserialization_error.get %}
+ {% if bundle.deserialization_error %}
<li><a href="#tab-deserialization-error">{% trans "Deserialization Error" %}</a></li>
{% endif %}
<li><a href="{% url dashboard_app.views.ajax_bundle_viewer bundle.pk %}">{% trans "Bundle Viewer" %}</a></li>
@@ -68,13 +95,13 @@
</div>
{% endif %}
- {% if bundle.deserialization_error.get %}
+ {% if bundle.deserialization_error %}
<div id="tab-deserialization-error">
<h3>Cause</h3>
- <p>{{ bundle.deserialization_error.get.error_message }}</p>
+ <p>{{ bundle.deserialization_error.error_message }}</p>
<h3>Deserialization failure traceback</h3>
<div style="overflow-x: scroll">
- {% stylize "pytb" %}{{ bundle.deserialization_error.get.traceback|safe }}{% endstylize %}
+ {% stylize "pytb" %}{{ bundle.deserialization_error.traceback|safe }}{% endstylize %}
</div>
</div>
{% endif %}
=== modified file 'dashboard_app/templates/dashboard_app/bundle_list.html'
@@ -55,7 +55,6 @@
$("#master-toolbar-splice").remove();
$("#master-toolbar").addClass("ui-widget-header ui-corner-tl ui-corner-tr").css(
"padding", "5pt").css("text-align", "center");
- new FixedHeader(oTable);
});
</script>
<table class="demo_jui display" id="bundles">
@@ -81,7 +80,7 @@
{% endif %}
</td>
<td>{{ bundle.is_deserialized|yesno }}</td>
- <td>{% if bundle.deserialization_error.get %}yes{% endif %}</td>
+ <td>{% if bundle.deserialization_error %}yes{% endif %}</td>
</tr>
{% endfor %}
</tbody>
=== modified file 'dashboard_app/templates/dashboard_app/front_page_snippet.html'
@@ -1,10 +1,29 @@
<p>Click on image description to see automatic QA report</p>
{% regroup dashboard.interesting_images by rootfs_type as rootfs_list %}
{% for rootfs in rootfs_list %}
-<h4>{{ rootfs.grouper|capfirst }}</h4>
-<ul>
- {% for image_health in rootfs.list %}
- <li><a href="{{ image_health.get_absolute_url }}">{{ image_health.rootfs_type }} + {{ image_health.hwpack_type }}</a></li>
- {% endfor %}
-</ul>
+<style type="text/css">
+ .column {
+ float: left;
+ width: 25em;
+ padding: 2pt;
+ }
+ .column h4 {
+ margin: 0;
+ }
+ .column ul {
+ padding: 0;
+ margin: 0 0 0 1em;
+ list-style-position: inside;
+ }
+
+</style>
+<div class="column">
+ <h4>{{ rootfs.grouper|capfirst }}</h4>
+ <ul>
+ {% for image_health in rootfs.list %}
+ <li><a href="{{ image_health.get_absolute_url }}">{{ image_health.rootfs_type }} + {{ image_health.hwpack_type }}</a></li>
+ {% endfor %}
+ </ul>
+</div>
{% endfor %}
+<div style="clear:both;"></div>
=== modified file 'dashboard_app/templates/dashboard_app/test_result_detail.html'
@@ -16,11 +16,11 @@
</div>
</div>
<br/>
-{% if test_result.test_run.test_results.count > 1 %}
+{% if test_result.test_run.get_results.count > 1 %}
<h3>Other results</h3>
<p class="hint">Results from the same test run are available here</p>
<select id="other_results">
- {% regroup test_result.test_run.test_results.all by test_case as test_result_group_list %}
+ {% regroup test_result.test_run.get_results by test_case as test_result_group_list %}
{% for test_result_group in test_result_group_list %}
{% if test_result_group.list|length > 1 %}<optgroup label="Results for test case {{ test_result_group.grouper }}">{% endif %}
{% for other_test_result in test_result_group.list %}
=== modified file 'dashboard_app/templates/dashboard_app/test_run_detail.html'
@@ -21,57 +21,173 @@
<th>{% trans "Result" %}</th>
<th>{% trans "Measurement" %}</th>
</tr>
- <tbody>
- {% for test_result in test_run.test_results.select_related.all %}
- <tr>
- <td width="1%">{{ test_result.relative_index }}</td>
- <td>{{ test_result.test_case|default_if_none:"<em>Not specified</em>" }}</td>
- <td>
- <a href ="{{test_result.get_absolute_url}}">
- <img src="{{ STATIC_URL }}dashboard_app/images/icon-{{ test_result.result_code }}.png"
- alt="{{ test_result.get_result_display }}" width="16" height="16" border="0"/></a>
- <a href ="{{test_result.get_absolute_url}}">{{ test_result.get_result_display }}</a>
- </td>
- <td>{{ test_result.measurement|default_if_none:"Not specified" }} {{ test_result.units }}</td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- {% endblock %}
+ </thead>
+ <tbody>
+ {% for test_result in test_run.get_results %}
+ <tr>
+ <td width="1%">{{ test_result.relative_index }}</td>
+ <td>{{ test_result.test_case|default_if_none:"<em>Not specified</em>" }}</td>
+ <td>
+ <a href ="{{test_result.get_absolute_url}}">
+ <img src="{{ STATIC_URL }}dashboard_app/images/icon-{{ test_result.result_code }}.png"
+ alt="{{ test_result.get_result_display }}" width="16" height="16" border="0"/></a>
+ <a href ="{{test_result.get_absolute_url}}">{{ test_result.get_result_display }}</a>
+ </td>
+ <td>{{ test_result.measurement|default_if_none:"Not specified" }} {{ test_result.units }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+{% endblock %}
{% block sidebar %}
-<dl>
- <dt>{% trans "Test Run UUID" %}</dt>
- <dd>{{ test_run.analyzer_assigned_uuid }} <a href="{% url dashboard_app.views.redirect_to_test_run test_run.analyzer_assigned_uuid %}">{% trans "permalink" %}</a></dd>
- <dt>{% trans "Test Name" %}</dt>
- <dd><a href="{{ test_run.test.get_absolute_url }}">{{ test_run.test }}</a></dd>
- <dt>{% trans "OS Distribution" %}</dt>
+<h3>Permalink</h3>
+<p>You can navigate to this test run, regardless of the bundle stream it is
+located in, by using this <a
+ href="{% url dashboard_app.views.redirect_to_test_run test_run.analyzer_assigned_uuid %}"
+ >permalink</a>.</p>
+
+<h3>Test run details</h3>
+<dl>
+ <dt>{% trans "Test Name:" %}</dt>
+ <dd><a href="{{ test_run.test.get_absolute_url }}">{{ test_run.test.test_id }}</a>
+ <p class="help_text">This is the identifier of the test that was invoked. A
+ test is a collection of test cases. Test is also the smallest piece of code
+ that can be invoked by lava-test.</p>
+ </dd>
+ <dt>{% trans "Test Run UUID:" %}</dt>
+ <dd><small>{{ test_run.analyzer_assigned_uuid }}</small>
+ <p class="help_text">This is a globally unique identifier that was assigned
+ by the log analyzer. Running the same test multiple times results in
+ different values of this identifier. The dashboard uses this identifier to
+ refer to a particular test run. It is preserved across different LAVA
+ installations, that is, if you pull test results (as bundles) from one system
+ to another this identifier remains intact</p>
+ </dd>
+ <dt>{% trans "Bundle SHA1:" %}</dt>
+ <dd><a href="{{ test_run.bundle.get_absolute_url }}"
+ ><small>{{ test_run.bundle.content_sha1 }}</small></a>
+ <p class="help_text">This is the SHA1 hash of the bundle that contains this test run.</p>
+ </dd>
+
+ <dt>{% trans "Attachments:" %}</dt>
+ <dd>
+ <ul>
+ {% for attachment in test_run.attachments.all %}
+ <li><a href="{{ attachment.get_absolute_url }}"
+ >{{ attachment }}</a>
+ {% if attachment.content %}
+ ({{ attachment.content.size|filesizeformat }})
+ {% endif %}
+ </li>
+ {% empty %}
+ <em>{% trans "There are no attachments associated with this test run." %}</em>
+ {% endfor %}
+ </ul>
+ <p class="help_text">LAVA can store attachments associated with a
+ particular test run. Those attachments can be used to store log files, crash
+ dumps, screen shots or other useful test artifacts.</p>
+ </dd>
+
+ <dt>{% trans "Tags:" %}</dt>
+ <dd>
+ <ul>
+ {% for tag in test_run.tags.all %}
+ <li><code>{{ tag }}</code></li>
+ {% empty %}
+ <em>{% trans "There are no tags associated with this test run." %}</em>
+ {% endfor %}
+ </ul>
+ <p class="help_text">LAVA can store tags associated with a particular
+ test run. Tags are simple strings like <q>project-foo-prerelase-testing</q>
+ or <q>linaro-image-2011-09-27</q>. Tags can be used by the testing effort
+ feature to group results together.</p>
+ </dd>
+</dl>
+
+<h3>Software context</h3>
+<dl>
+ <dt>{% trans "OS Distribution:" %}</dt>
<dd>{{ test_run.sw_image_desc|default:"<i>Unspecified</i>" }}</dd>
- <dt>{% trans "Bundle SHA1" %}</dt>
- <dd><a href="{{ test_run.bundle.get_absolute_url }}">{{ test_run.bundle.content_sha1 }}</a></dd>
- <dt>{% trans "Time check performed" %}</dt>
- <dd>{{ test_run.time_check_performed|yesno }}</dd>
+ <dt>{% trans "Software packages:" %}</dt>
+ <dd><a href="{% url dashboard_app.views.test_run_software_context test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
+ >See all {{ test_run.packages.all.count }} software packages</a>
+ <p class="help_text">LAVA keeps track of all the software packages (such as
+ Debian packages managed with dpkg) that were installed prior to running a
+ test. This information can help you track down errors caused by a particular
+ buggy dependency</p>
+ </dd>
+ <dt>{% trans "Software sources:" %}</dt>
+ <dd><a href="{% url dashboard_app.views.test_run_software_context test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
+ >See all {{ test_run.sources.all.count }} source references</a>
+ <p class="help_text">LAVA can track more data than just package name and
+ version. You can track precise software information such as the version
+ control system branch or repository, revision or tag name and more</p>
+ </dd>
+</dl>
+
+<h3>Hardware context</h3>
+<dl>
+ <dt>{% trans "Board:" %}</dt>
+ <dd>{{ test_run.get_board|default_if_none:"There are no boards associated with this test run" }}</dd>
+ <dt>{% trans "Other devices:" %}</dt>
+ <dd><a
+ href="{% url dashboard_app.views.test_run_hardware_context test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
+ >See all {{ test_run.devices.all.count }} devices</a>
+ <p class="help_text">LAVA keeps track of the hardware that was used for
+ testing. This can help cross-reference benchmarks and identify
+ hardware-specific issues.</p>
+ </dd>
+</dl>
+
+<h3>Custom attributes</h3>
+<p class="help_text">LAVA can store arbitrary key-value attributes associated
+with each test run (and separately, each test result)</p>
+<ul>
+ {% for attribute in test_run.attributes.all %}
+ <li>{{ attribute.name }} = {{ attribute.value }}</li>
+ {% empty %}
+ <em>{% trans "There are no attributes associated with this test run." %}</em>
+ {% endfor %}
+</ul>
+
+<h3>Time stamps</h3>
+<p class="help_text">There are three different timestamps
+associated with each test run. They are explained below.</p>
+<dl>
<dt>{% trans "Log analyzed on:" %}</dt>
<dd>
{{ test_run.analyzer_assigned_date|naturalday }}
{{ test_run.analyzer_assigned_date|time }}
- </dd>
- <dt>{% trans "Data uploaded on:" %}</dt>
+ ({{ test_run.analyzer_assigned_date|timesince }} ago)
+ <p class="help_text">This is the moment this that this test run's artifacts
+ (such as log files and other output) were processed by the log analyzer.
+ Typically the analyzer is a part of lava-test framework and test output is
+ analyzed on right on the device so this time may not be trusted, see below
+ for the description of <q>time check performed</q></p>
+ </dd>
+ <dt>{% trans "Time check performed" %}</dt>
+ <dd>{{ test_run.time_check_performed|yesno }}
+ <p class="help_text">The value <em>no</em> indicates that the log analyzer
+ was not certain that the time and date is accurate.</p>
+ </dd>
+ <dt>{% trans "Data imported on:" %}</dt>
<dd>
{{ test_run.import_assigned_date|naturalday }}
{{ test_run.import_assigned_date|time }}
+ ({{ test_run.import_assigned_date|timesince }} ago)
+ <p class="help_text">This is the moment this test run entry was created in
+ the LAVA database. It can differ from upload date if there were any initial
+ deserialization problems and the data was deserialized later.</p>
</dd>
- <dt>{% trans "Other information:" %}</dt>
- <dd><a
- href="{% url dashboard_app.views.test_run_hardware_context test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
- >{% trans "Hardware context" %} ({{ test_run.devices.all.count }} devices)</a></dd>
- <dd><a
- href="{% url dashboard_app.views.test_run_software_context test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
- >{% trans "Software context" %} ({{ test_run.packages.all.count }} packages, {{ test_run.sources.all.count }} sources)</a></dd>
- <dd><a
- href="{% url dashboard_app.views.attachment_list test_run.bundle.bundle_stream.pathname test_run.bundle.content_sha1 test_run.analyzer_assigned_uuid %}"
- >{% trans "Attachments" %} ({{ test_run.attachments.count }})</a></dd>
+ <dt>{% trans "Data uploaded on:" %}</dt>
+ <dd>
+ {{ test_run.bundle.uploaded_on|naturalday }}
+ {{ test_run.bundle.uploaded_on|time }}
+ ({{ test_run.bundle.uploaded_on|timesince }} ago)
+ <p class="help_text">This is the moment this data was first uploaded to LAVA
+ (as a serialized bundle).</p>
</dd>
</dl>
{% endblock %}
=== modified file 'dashboard_app/templates/dashboard_app/test_run_list.html'
@@ -53,7 +53,6 @@
$("#master-toolbar-splice").remove();
$("#master-toolbar").addClass("ui-widget-header ui-corner-tl ui-corner-tr").css(
"padding", "5pt").css("text-align", "center");
- new FixedHeader(oTable);
});
</script>
{% include "dashboard_app/_test_run_list_table.html" %}
=== added file 'dashboard_app/templates/dashboard_app/testing_effort_detail.html'
@@ -0,0 +1,105 @@
+{% extends "dashboard_app/_content_with_sidebar.html" %}
+{% load humanize %}
+{% load markup %}
+{% load i18n %}
+
+
+{% block extrahead %}
+{{ block.super }}
+<!--[if IE]><script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/excanvas.min.js"></script><![endif]-->
+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.min.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.stack.min.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.axislabels.js"></script>
+{% endblock %}
+
+
+{% block sidebar %}
+<h3>{{ effort }}</h3>
+{{ effort.description|markdown }}
+<h3>Tags</h3>
+<p class="help_text">The concept of <q>testing efforts</q> is based on using
+tags to associate test runs with a common goal or task. This testing effort
+will list any test runs that have <strong>any</strong> of the following tags
+present.</p>
+<ul>
+ {% for tag in effort.tags.all %}
+ <li>{{ tag }}</li>
+ {% empty %}
+ <em>This testing effort has not defined any tags yet, tests
+ runs will not show up unless this is done</em>
+ {% endfor %}
+</ul>
+{% endblock %}
+
+
+{% block content %}
+<style type="text/css">
+ .result_pass {
+ background-color: #3aad3a;
+ }
+
+ .result_fail {
+ background-color: #ff3800;
+ }
+
+ .result_skip {
+ background-color: yellow;
+ }
+
+ .result_unknown {
+ background-color: #aaad9c;
+ }
+
+ .helper {
+ float: left;
+ height: 1em;
+ margin-left: 1px;
+ }
+
+ table.special {
+ border-collapse: collapse;
+ width: 100%;
+ }
+
+ table.special th {
+ text-align: left;
+ border-bottom: 1px solid #333;
+ padding: 2pt;
+ }
+</style>
+<table class="special">
+ {% regroup test_run_list|dictsortreversed:"analyzer_assigned_date" by analyzer_assigned_date|date:"Y-m-d" as test_run_cluster_list %}
+ {% for test_run_cluster in test_run_cluster_list %}
+ <tr>
+ <th colspan="2">Tests ran on {{ test_run_cluster.grouper }}</th>
+ <th>Pass</th>
+ <th>Fail</th>
+ <th>Skip</th>
+ <th>Unknown</th>
+ </tr>
+ {% for test_run in test_run_cluster.list %}
+ <tr>
+ <td><a href="{{ test_run.get_absolute_url }}">{{ test_run.test }}</a></td>
+ {% with test_run.denormalization as denormalization %}
+ <td>
+ {% spaceless %}
+ <div class="result_pass helper"
+ style="width: {% widthratio denormalization.count_pass denormalization.count_all 500 %}px;"></div>
+ <div class="result_fail helper"
+ style="width: {% widthratio denormalization.count_fail denormalization.count_all 500 %}px;"></div>
+ <div class="result_skip helper"
+ style="width: {% widthratio denormalization.count_skip denormalization.count_all 500 %}px;"></div>
+ <div class="result_unknown helper"
+ style="width: {% widthratio denormalization.count_unknown denormalization.count_all 500 %}px;"></div>
+ {% endspaceless %}
+ </td>
+ <td>{{ denormalization.count_pass }}</td>
+ <td>{{ denormalization.count_fail }}</td>
+ <td>{{ denormalization.count_skip }}</td>
+ <td>{{ denormalization.count_unknown }}</td>
+ {% endwith %}
+ </tr>
+ {% endfor %}
+ {% endfor %}
+</table>
+{% endblock %}
=== added file 'dashboard_app/templates/dashboard_app/testing_effort_list.html'
@@ -0,0 +1,19 @@
+{% extends "dashboard_app/_content.html" %}
+{% load humanize %}
+{% load markup %}
+{% load i18n %}
+
+
+{% block content %}
+<h2>Testing efforts</h2>
+{% regroup effort_list by project as effort_group_list %}
+{% for effort_group in effort_group_list %}
+<h3>In project <a href="{{ effort_group.grouper.get_absolute_url }}">{{ effort_group.grouper }}</a></h3>
+<dl>
+ {% for effort in effort_group.list %}
+ <dt><a href="{{ effort.get_absolute_url }}">{{ effort }}</a></dt>
+ <dd>{{ effort.description|markdown }}</dd>
+ {% endfor %}
+</dl>
+{% endfor %}
+{% endblock %}
=== modified file 'dashboard_app/tests/models/bundle.py'
@@ -87,7 +87,7 @@
self.mocker.replay()
self.bundle.deserialize(False)
self.assertFalse(self.bundle.is_deserialized)
- self.assertEqual(self.bundle.deserialization_error.get().error_message, "boom")
+ self.assertEqual(self.bundle.deserialization_error.error_message, "boom")
def test_deserialize_ignores_deserialized_bundles(self):
# just reply as we're not using mocker in this test case
=== modified file 'dashboard_app/tests/other/deserialization.py'
@@ -516,6 +516,48 @@
("attr2", "value2")]))
+class Bundle13DeserializerSuccessTests(TestCase):
+
+ json_text = '''
+ {
+ "format": "Dashboard Bundle Format 1.3",
+ "test_runs": [
+ {
+ "test_id": "some_test_id",
+ "analyzer_assigned_uuid": "1ab86b36-c23d-11df-a81b-002163936223",
+ "analyzer_assigned_date": "2010-12-31T23:59:59Z",
+ "time_check_performed": true,
+ "test_results": [ ],
+ "tags": [
+ "tag-1",
+ "tag-2"
+ ]
+ }
+ ]
+ }
+ '''
+
+ def setUp(self):
+ super(Bundle13DeserializerSuccessTests, self).setUp()
+ self.s_bundle = fixtures.create_bundle(
+ '/anonymous/', self.json_text, 'bundle.json')
+ # Decompose the data here
+ self.s_bundle.deserialize(prefer_evolution=False)
+ if not self.s_bundle.is_deserialized:
+ raise AssertionError("Deserialzation failed:" + self.s_bundle.deserialization_error.get().traceback)
+ # Link to test run for easier testing
+ self.s_test = self.s_bundle.test_runs.get()
+
+ def tearDown(self):
+ self.s_bundle.delete_files()
+ super(Bundle13DeserializerSuccessTests, self).tearDown()
+
+ def test_deserialize_tags(self):
+ self.assertEqual(self.s_test.tags.count(), 2)
+ self.assertEqual([tag.name for tag in self.s_test.tags.order_by('name').all()],
+ ["tag-1", "tag-2"])
+
+
class BundleDeserializerFailureTestCase(TestCaseWithScenarios):
scenarios = [
@@ -690,7 +732,7 @@
# better than not knowing what really happened and hiding other
# potential bugs that would otherwise be masked here.
self.assertIn(
- self.s_bundle.deserialization_error.get().error_message, [
+ self.s_bundle.deserialization_error.error_message, [
'A test with UUID 1ab86b36-c23d-11df-a81b-002163936223 already exists',
'column analyzer_assigned_uuid is not unique',
u'duplicate key value violates unique constraint '
=== modified file 'dashboard_app/urls.py'
@@ -65,4 +65,6 @@
url(r'^image_status/$', 'image_status_list'),
url(r'^image_status/(?P<rootfs_type>[a-zA-Z0-9_-]+)\+(?P<hwpack_type>[a-zA-Z0-9_-]+)/$', 'image_status_detail'),
url(r'^image_status/(?P<rootfs_type>[a-zA-Z0-9_-]+)\+(?P<hwpack_type>[a-zA-Z0-9_-]+)/test-history/(?P<test_id>[^/]+)/$', 'image_test_history'),
+ url(r'^efforts/$', 'testing_effort_list'),
+ url(r'^efforts/(?P<pk>[0-9]+)/$', 'testing_effort_detail'),
)
=== modified file 'dashboard_app/views.py'
@@ -22,6 +22,7 @@
import json
+from django.contrib.sites.models import Site
from django.db.models.manager import Manager
from django.db.models.query import QuerySet
from django.http import Http404, HttpResponse
@@ -39,6 +40,7 @@
Test,
TestResult,
TestRun,
+ TestingEffort,
)
from dashboard_app.bread_crumbs import BreadCrumb, BreadCrumbTrail
@@ -126,7 +128,7 @@
)
return object_list(
request,
- queryset=bundle_stream.bundles.all().order_by('-uploaded_on'),
+ queryset=bundle_stream.bundles.select_related('bundle_stream', 'deserialization_error').order_by('-uploaded_on'),
template_name="dashboard_app/bundle_list.html",
template_object_name="bundle",
extra_context={
@@ -163,6 +165,7 @@
bundle_detail,
pathname=pathname,
content_sha1=content_sha1),
+ "site": Site.objects.get_current(),
"bundle_stream": bundle_stream
})
@@ -226,7 +229,22 @@
test_run_list,
pathname=pathname),
"test_run_list": TestRun.objects.filter(
- bundle__bundle_stream=bundle_stream),
+ bundle__bundle_stream=bundle_stream
+ ).order_by( # clean any implicit ordering
+ ).select_related(
+ "test",
+ "bundle",
+ "bundle__bundle_stream",
+ "test_results"
+ ).only(
+ "analyzer_assigned_uuid", # needed by TestRun.__unicode__
+ "analyzer_assigned_date", # used by the view
+ "bundle__uploaded_on", # needed by Bundle.get_absolute_url
+ "bundle__content_sha1", # needed by Bundle.get_absolute_url
+ "bundle__bundle_stream__pathname", # Needed by TestRun.get_absolute_url
+ "test__name", # needed by Test.__unicode__
+ "test__test_id", # needed by Test.__unicode__
+ ),
"bundle_stream": bundle_stream,
}, RequestContext(request)
)
@@ -554,3 +572,37 @@
test=test,
test_id=test_id),
}, RequestContext(request))
+
+
+@BreadCrumb("Testing efforts", parent=index)
+def testing_effort_list(request):
+ return render_to_response(
+ "dashboard_app/testing_effort_list.html", {
+ 'effort_list': TestingEffort.objects.all(
+ ).order_by('name'),
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ testing_effort_list),
+ }, RequestContext(request))
+
+
+@BreadCrumb(
+ "{effort}",
+ parent=testing_effort_list,
+ needs=["pk"])
+def testing_effort_detail(request, pk):
+ effort = get_object_or_404(TestingEffort, pk=pk)
+ return render_to_response(
+ "dashboard_app/testing_effort_detail.html", {
+ 'effort': effort,
+ 'test_run_list': effort.get_test_runs(
+ ).select_related(
+ 'denormalization',
+ 'bundle',
+ 'bundle__bundle_stream',
+ 'test',
+ ),
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ testing_effort_detail,
+ effort=effort,
+ pk=pk),
+ }, RequestContext(request))
=== modified file 'dashboard_app/xmlrpc.py'
@@ -443,7 +443,7 @@
if bundle.is_deserialized is False:
raise xmlrpclib.Fault(
errors.CONFLICT,
- bundle.deserialization_error.get().error_message)
+ bundle.deserialization_error.error_message)
return True
def make_stream(self, pathname, name):