diff mbox

[Branch,~linaro-validation/lava-dashboard/trunk] Rev 422: Add Image report editor feature.

Message ID 20130913141724.18242.49559.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

Stevan Radakovic Sept. 13, 2013, 2:17 p.m. UTC
Merge authors:
  Stevan Radaković (stevanr)
Related merge proposals:
  https://code.launchpad.net/~stevanr/lava-dashboard/image-report-editor-frontend/+merge/185255
  proposed by: Stevan Radaković (stevanr)
  review: Approve - Antonio Terceiro (terceiro)
------------------------------------------------------------
revno: 422 [merge]
committer: Stevan Radakovic <stevan.radakovic@linaro.org>
branch nick: trunk
timestamp: Fri 2013-09-13 16:16:05 +0200
message:
  Add Image report editor feature.
added:
  dashboard_app/migrations/0031_auto__del_imagecharttestrun__add_imagecharttest__add_unique_imagechart.py
  dashboard_app/static/dashboard_app/css/image-charts.css
  dashboard_app/static/dashboard_app/images/ajax-progress.gif
  dashboard_app/static/dashboard_app/js/image-report-editor.js
  dashboard_app/templates/dashboard_app/image_chart_filter_form.html
  dashboard_app/templates/dashboard_app/image_report_chart_detail.html
  dashboard_app/templates/dashboard_app/image_report_chart_form.html
  dashboard_app/templates/dashboard_app/image_report_detail.html
  dashboard_app/templates/dashboard_app/image_report_form.html
  dashboard_app/templates/dashboard_app/image_report_list.html
  dashboard_app/views/image_reports/
  dashboard_app/views/image_reports/__init__.py
  dashboard_app/views/image_reports/forms.py
  dashboard_app/views/image_reports/views.py
modified:
  dashboard_app/models.py
  dashboard_app/urls.py
  dashboard_app/views/filters/tables.py
  dashboard_app/views/filters/views.py


--
lp:lava-dashboard
https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk

You are subscribed to branch lp:lava-dashboard.
To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-dashboard/trunk/+edit-subscription
diff mbox

Patch

=== added file 'dashboard_app/migrations/0031_auto__del_imagecharttestrun__add_imagecharttest__add_unique_imagechart.py'
--- dashboard_app/migrations/0031_auto__del_imagecharttestrun__add_imagecharttest__add_unique_imagechart.py	1970-01-01 00:00:00 +0000
+++ dashboard_app/migrations/0031_auto__del_imagecharttestrun__add_imagecharttest__add_unique_imagechart.py	2013-09-13 11:45:52 +0000
@@ -0,0 +1,388 @@ 
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Deleting model 'ImageChartTestRun'
+        db.delete_table('dashboard_app_imagecharttestrun')
+
+        # Adding model 'ImageChartTest'
+        db.create_table('dashboard_app_imagecharttest', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('image_chart_filter', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.ImageChartFilter'])),
+            ('test', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.Test'])),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+        ))
+        db.send_create_signal('dashboard_app', ['ImageChartTest'])
+
+        # Adding unique constraint on 'ImageChartTest', fields ['image_chart_filter', 'test']
+        db.create_unique('dashboard_app_imagecharttest', ['image_chart_filter_id', 'test_id'])
+
+        # Adding model 'ImageChartFilter'
+        db.create_table('dashboard_app_imagechartfilter', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('image_chart', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.ImageReportChart'])),
+            ('filter', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.TestRunFilter'], null=True, on_delete=models.SET_NULL)),
+            ('representation', self.gf('django.db.models.fields.CharField')(default='lines', max_length=20)),
+        ))
+        db.send_create_signal('dashboard_app', ['ImageChartFilter'])
+
+        # Deleting field 'ImageChartTestCase.image_chart'
+        db.delete_column('dashboard_app_imagecharttestcase', 'image_chart_id')
+
+        # Adding field 'ImageChartTestCase.image_chart_filter'
+        db.add_column('dashboard_app_imagecharttestcase', 'image_chart_filter',
+                      self.gf('django.db.models.fields.related.ForeignKey')(default=0, to=orm['dashboard_app.ImageChartFilter']),
+                      keep_default=False)
+
+        # Adding unique constraint on 'ImageChartTestCase', fields ['image_chart_filter', 'test_case']
+        db.create_unique('dashboard_app_imagecharttestcase', ['image_chart_filter_id', 'test_case_id'])
+
+        # Adding field 'ImageReport.is_published'
+        db.add_column('dashboard_app_imagereport', 'is_published',
+                      self.gf('django.db.models.fields.BooleanField')(default=False),
+                      keep_default=False)
+
+        # Deleting field 'ImageReportChart.representation'
+        db.delete_column('dashboard_app_imagereportchart', 'representation')
+
+        # Adding field 'ImageReportChart.description'
+        db.add_column('dashboard_app_imagereportchart', 'description',
+                      self.gf('django.db.models.fields.TextField')(null=True, blank=True),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Removing unique constraint on 'ImageChartTestCase', fields ['image_chart_filter', 'test_case']
+        db.delete_unique('dashboard_app_imagecharttestcase', ['image_chart_filter_id', 'test_case_id'])
+
+        # Removing unique constraint on 'ImageChartTest', fields ['image_chart_filter', 'test']
+        db.delete_unique('dashboard_app_imagecharttest', ['image_chart_filter_id', 'test_id'])
+
+        # Adding model 'ImageChartTestRun'
+        db.create_table('dashboard_app_imagecharttestrun', (
+            ('test_run', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.TestRun'])),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+            ('image_chart', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.ImageReportChart'])),
+        ))
+        db.send_create_signal('dashboard_app', ['ImageChartTestRun'])
+
+        # Deleting model 'ImageChartTest'
+        db.delete_table('dashboard_app_imagecharttest')
+
+        # Deleting model 'ImageChartFilter'
+        db.delete_table('dashboard_app_imagechartfilter')
+
+        # Adding field 'ImageChartTestCase.image_chart'
+        db.add_column('dashboard_app_imagecharttestcase', 'image_chart',
+                      self.gf('django.db.models.fields.related.ForeignKey')(default=0, to=orm['dashboard_app.ImageReportChart']),
+                      keep_default=False)
+
+        # Deleting field 'ImageChartTestCase.image_chart_filter'
+        db.delete_column('dashboard_app_imagecharttestcase', 'image_chart_filter_id')
+
+        # Deleting field 'ImageReport.is_published'
+        db.delete_column('dashboard_app_imagereport', 'is_published')
+
+        # Adding field 'ImageReportChart.representation'
+        db.add_column('dashboard_app_imagereportchart', 'representation',
+                      self.gf('django.db.models.fields.CharField')(default='pass/fail', max_length=20),
+                      keep_default=False)
+
+        # Deleting field 'ImageReportChart.description'
+        db.delete_column('dashboard_app_imagereportchart', 'description')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'dashboard_app.attachment': {
+            'Meta': {'object_name': 'Attachment'},
+            'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+            'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'})
+        },
+        'dashboard_app.bundle': {
+            'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
+            '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
+            '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
+            'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
+            'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
+            'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
+        },
+        'dashboard_app.bundledeserializationerror': {
+            'Meta': {'object_name': 'BundleDeserializationError'},
+            'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}),
+            'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'})
+        },
+        'dashboard_app.bundlestream': {
+            'Meta': {'object_name': 'BundleStream'},
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.hardwaredevice': {
+            'Meta': {'object_name': 'HardwareDevice'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'dashboard_app.image': {
+            'Meta': {'object_name': 'Image'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['dashboard_app.TestRunFilter']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'})
+        },
+        'dashboard_app.imagechartfilter': {
+            'Meta': {'object_name': 'ImageChartFilter'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_chart': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.ImageReportChart']"}),
+            'representation': ('django.db.models.fields.CharField', [], {'default': "'lines'", 'max_length': '20'})
+        },
+        'dashboard_app.imagecharttest': {
+            'Meta': {'unique_together': "(('image_chart_filter', 'test'),)", 'object_name': 'ImageChartTest'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_chart_filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.ImageChartFilter']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Test']"})
+        },
+        'dashboard_app.imagecharttestcase': {
+            'Meta': {'unique_together': "(('image_chart_filter', 'test_case'),)", 'object_name': 'ImageChartTestCase'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_chart_filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.ImageChartFilter']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'test_case': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestCase']"})
+        },
+        'dashboard_app.imagereport': {
+            'Meta': {'object_name': 'ImageReport'},
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'})
+        },
+        'dashboard_app.imagereportchart': {
+            'Meta': {'object_name': 'ImageReportChart'},
+            'chart_type': ('django.db.models.fields.CharField', [], {'default': "'pass/fail'", 'max_length': '20'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_report': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['dashboard_app.ImageReport']"}),
+            'is_data_table_visible': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_interactive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'target_goal': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '10', 'decimal_places': '5', 'blank': 'True'})
+        },
+        'dashboard_app.imageset': {
+            'Meta': {'object_name': 'ImageSet'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
+        },
+        'dashboard_app.launchpadbug': {
+            'Meta': {'object_name': 'LaunchpadBug'},
+            'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"})
+        },
+        'dashboard_app.namedattribute': {
+            'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'value': ('django.db.models.fields.TextField', [], {})
+        },
+        'dashboard_app.pmqabundlestream': {
+            'Meta': {'object_name': 'PMQABundleStream'},
+            'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.BundleStream']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'dashboard_app.softwarepackage': {
+            'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'dashboard_app.softwarepackagescratch': {
+            'Meta': {'object_name': 'SoftwarePackageScratch'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'dashboard_app.softwaresource': {
+            'Meta': {'object_name': 'SoftwareSource'},
+            'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
+            'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'})
+        },
+        'dashboard_app.tag': {
+            'Meta': {'object_name': 'Tag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'})
+        },
+        'dashboard_app.test': {
+            'Meta': {'object_name': 'Test'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+            'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
+        },
+        'dashboard_app.testcase': {
+            'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}),
+            'test_case_id': ('django.db.models.fields.TextField', [], {}),
+            'units': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'dashboard_app.testdefinition': {
+            'Meta': {'object_name': 'TestDefinition'},
+            'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'environment': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'format': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'default': "'LOCAL'", 'max_length': '64'}),
+            'mime_type': ('django.db.models.fields.CharField', [], {'default': "'text/plain'", 'max_length': '64'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
+            'target_dev_types': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
+            'target_os': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '256'})
+        },
+        'dashboard_app.testresult': {
+            'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'},
+            '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}),
+            'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}),
+            'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'dashboard_app.testrun': {
+            'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'},
+            'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
+            'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}),
+            'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}),
+            'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}),
+            'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}),
+            'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        'dashboard_app.testrundenormalization': {
+            'Meta': {'object_name': 'TestRunDenormalization'},
+            'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"})
+        },
+        'dashboard_app.testrunfilter': {
+            'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'},
+            'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['auth.User']"})
+        },
+        'dashboard_app.testrunfilterattribute': {
+            'Meta': {'object_name': 'TestRunFilterAttribute'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'dashboard_app.testrunfiltersubscription': {
+            'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'dashboard_app.testrunfiltertest': {
+            'Meta': {'object_name': 'TestRunFilterTest'},
+            'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tests'", 'to': "orm['dashboard_app.TestRunFilter']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.Test']"})
+        },
+        'dashboard_app.testrunfiltertestcase': {
+            'Meta': {'object_name': 'TestRunFilterTestCase'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cases'", 'to': "orm['dashboard_app.TestRunFilterTest']"}),
+            'test_case': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.TestCase']"})
+        }
+    }
+
+    complete_apps = ['dashboard_app']
\ No newline at end of file

=== modified file 'dashboard_app/models.py'
--- dashboard_app/models.py	2013-09-05 16:00:38 +0000
+++ dashboard_app/models.py	2013-09-13 14:16:05 +0000
@@ -1540,101 +1540,6 @@ 
         return self.name
 
 
-class ImageReport(models.Model):
-
-    name = models.SlugField(max_length=1024, unique=True)
-
-    description = models.TextField(blank=True, null=True)
-
-    def __unicode__(self):
-        return self.name
-
-
-# Chart types
-CHART_TYPES = ((r'pass/fail', 'Pass/Fail'),
-               (r'measurement', 'Measurement'))
-# Chart representation
-REPRESENTATION_TYPES = ((r'lines', 'Lines'),
-                        (r'bars', 'Bars'))
-
-
-class ImageReportChart(models.Model):
-
-    name = models.CharField(max_length=100)
-
-    image_report = models.ForeignKey(
-        ImageReport,
-        default=None,
-        null=False,
-        on_delete=models.CASCADE)
-
-    test_runs = models.ManyToManyField(
-        TestRun,
-        through='ImageChartTestRun')
-
-    test_cases = models.ManyToManyField(
-        TestCase,
-        through='ImageChartTestCase')
-
-    chart_type = models.CharField(
-        max_length=20,
-        choices=CHART_TYPES,
-        verbose_name='Chart type')
-
-    representation = models.CharField(
-        max_length=20,
-        choices=REPRESENTATION_TYPES,
-        verbose_name='Representation type')
-
-    target_goal = models.DecimalField(
-        blank = True,
-        decimal_places = 5,
-        max_digits = 10,
-        null = True,
-        verbose_name = 'Target goal')
-
-    is_interactive = models.BooleanField(
-        default=False,
-        verbose_name='Chart is interactive')
-
-    is_data_table_visible = models.BooleanField(
-        default=False,
-        verbose_name='Data table is visible')
-
-    def __unicode__(self):
-        return self.name
-
-
-class ImageChartTestRun(models.Model):
-
-    image_chart = models.ForeignKey(
-        ImageReportChart,
-        null=False,
-        on_delete=models.CASCADE)
-
-    test_run = models.ForeignKey(
-        TestRun,
-        null=False,
-        on_delete=models.CASCADE)
-
-    name = models.CharField(max_length=200)
-
-
-class ImageChartTestCase(models.Model):
-
-    image_chart = models.ForeignKey(
-        ImageReportChart,
-        null=False,
-        on_delete=models.CASCADE)
-
-    test_case = models.ForeignKey(
-        TestCase,
-        null=False,
-        on_delete=models.CASCADE)
-
-    name = models.CharField(max_length=200)
-
-
 class LaunchpadBug(models.Model):
 
     bug_id = models.PositiveIntegerField(unique=True)
@@ -1923,3 +1828,137 @@ 
 class PMQABundleStream(models.Model):
 
     bundle_stream = models.ForeignKey(BundleStream, related_name='+')
+
+
+class ImageReport(models.Model):
+
+    name = models.SlugField(max_length=1024, unique=True)
+
+    description = models.TextField(blank=True, null=True)
+
+    is_published = models.BooleanField(
+        default=False,
+        verbose_name='Published')
+
+    def __unicode__(self):
+        return self.name
+
+    @models.permalink
+    def get_absolute_url(self):
+        return ("dashboard_app.views.image_reports.views.image_report_detail",
+                (), dict(name=self.name))
+
+# Chart types
+CHART_TYPES = ((r'pass/fail', 'Pass/Fail'),
+               (r'measurement', 'Measurement'))
+# Chart representation
+REPRESENTATION_TYPES = ((r'lines', 'Lines'),
+                        (r'bars', 'Bars'))
+
+
+class ImageReportChart(models.Model):
+
+    name = models.CharField(max_length=100)
+
+    description = models.TextField(blank=True, null=True)
+
+    image_report = models.ForeignKey(
+        ImageReport,
+        default=None,
+        null=False,
+        on_delete=models.CASCADE)
+
+    chart_type = models.CharField(
+        max_length=20,
+        choices=CHART_TYPES,
+        verbose_name='Chart type',
+        blank=False,
+        default="pass/fail",
+        )
+
+    target_goal = models.DecimalField(
+        blank = True,
+        decimal_places = 5,
+        max_digits = 10,
+        null = True,
+        verbose_name = 'Target goal')
+
+    is_interactive = models.BooleanField(
+        default=False,
+        verbose_name='Chart is interactive')
+
+    is_data_table_visible = models.BooleanField(
+        default=False,
+        verbose_name='Data table is visible')
+
+    def __unicode__(self):
+        return self.name
+
+    @models.permalink
+    def get_absolute_url(self):
+        return ("dashboard_app.views.image_reports.views.image_chart_detail",
+                (), dict(id=self.id))
+
+
+class ImageChartFilter(models.Model):
+
+    image_chart = models.ForeignKey(
+        ImageReportChart,
+        null=False,
+        on_delete=models.CASCADE)
+
+    filter = models.ForeignKey(
+        TestRunFilter,
+        null=True,
+        on_delete=models.SET_NULL)
+
+    representation = models.CharField(
+        max_length=20,
+        choices=REPRESENTATION_TYPES,
+        verbose_name='Representation',
+        blank=False,
+        default="lines",
+        )
+
+    @models.permalink
+    def get_absolute_url(self):
+        return (
+            "dashboard_app.views.image_reports.views.image_chart_filter_edit",
+            (), dict(id=self.id))
+
+
+class ImageChartTest(models.Model):
+
+    class Meta:
+        unique_together = ("image_chart_filter", "test")
+
+    image_chart_filter = models.ForeignKey(
+        ImageChartFilter,
+        null=False,
+        on_delete=models.CASCADE)
+
+    test = models.ForeignKey(
+        Test,
+        null=False,
+        on_delete=models.CASCADE)
+
+    name = models.CharField(max_length=200)
+
+
+class ImageChartTestCase(models.Model):
+
+    class Meta:
+        unique_together = ("image_chart_filter", "test_case")
+
+    image_chart_filter = models.ForeignKey(
+        ImageChartFilter,
+        null=False,
+        on_delete=models.CASCADE)
+
+    test_case = models.ForeignKey(
+        TestCase,
+        null=False,
+        on_delete=models.CASCADE)
+
+    name = models.CharField(max_length=200)
+

=== added file 'dashboard_app/static/dashboard_app/css/image-charts.css'
--- dashboard_app/static/dashboard_app/css/image-charts.css	1970-01-01 00:00:00 +0000
+++ dashboard_app/static/dashboard_app/css/image-charts.css	2013-09-12 14:12:07 +0000
@@ -0,0 +1,82 @@ 
+@import url("../../admin/css/widgets.css");
+
+div.selector { clear: both; }
+div.selector span.helptext { display: none; }
+div.selector h2 { margin: 0; font-size: 11pt; }
+div.selector a { text-decoration: none; }
+div.selector select { height: 10em; }
+div.selector ul.selector-chooser { margin-top: 5.5em; }
+div.selector .selector-chosen select {
+  border: 1px solid rgb(204, 204, 204);
+  border-top: none;
+}
+
+.list-container {
+    border: 1px solid #000000;
+    clear: both;
+    margin: 10px 10px 10px 10px;
+    padding: 10px;
+    width: 50%;
+}
+
+.form-field {
+    margin-bottom: 5px;
+    vertical-align: top;
+}
+
+.form-field label {
+    vertical-align: top;
+    width: 100px;
+    display: inline-block;
+    margin-left: 10px;
+}
+
+.submit-button {
+    margin-top: 20px;
+    margin-left: 10px;
+}
+
+.filter-headline {
+    font-weight: bold;
+    font-size: 16px;
+}
+
+.filter-container {
+    margin-bottom: 10px;
+    clear: both;
+}
+
+.filter-title {
+    font-weight: bold;
+    font-size: 15px;
+    margin-bottom: 10px;
+}
+
+.chart-title {
+    font-weight: bold;
+    font-size: 15px;
+    margin-bottom: 10px;
+}
+
+.errors {
+    color: red;
+}
+
+.fields-container {
+    margin-left: 10px;
+}
+
+#filters_div {
+    margin: 10px 0 0 10px;
+    border: 1px solid #000000;
+    clear: both;
+    width: 75%;
+    padding: 5px 0 10px 10px;
+    overflow: auto;
+}
+
+#alias_container {
+    font-weight: bold;
+    float: left;
+    display: none;
+}
\ No newline at end of file

=== added file 'dashboard_app/static/dashboard_app/images/ajax-progress.gif'
Binary files dashboard_app/static/dashboard_app/images/ajax-progress.gif	1970-01-01 00:00:00 +0000 and dashboard_app/static/dashboard_app/images/ajax-progress.gif	2013-09-09 11:47:00 +0000 differ
=== added file 'dashboard_app/static/dashboard_app/js/image-report-editor.js'
--- dashboard_app/static/dashboard_app/js/image-report-editor.js	1970-01-01 00:00:00 +0000
+++ dashboard_app/static/dashboard_app/js/image-report-editor.js	2013-09-12 14:24:11 +0000
@@ -0,0 +1,221 @@ 
+select_filter = function() {
+    // Open the filter select dialog.
+    $('#filter_select_dialog').dialog('open');
+}
+
+filters_callback = function(id, name) {
+    // Function which will be called when a filter is selected from the dialog.
+
+    if ($('#id_chart_type').val() == "pass/fail") {
+        url = "/dashboard/filters/+get-tests-json";
+    } else {
+        url = "/dashboard/filters/+get-test-cases-json";
+    }
+
+    $.ajax({
+        url: url,
+        async: false,
+        data: {"id": id},
+        beforeSend: function () {
+            $('#filter-container').remove();
+            $('#filter_select_dialog').dialog('close');
+            $('#loading_dialog').dialog('open');
+        },
+        success: function (data) {
+            $('#loading_dialog').dialog('close');
+            $("#id_filter").val(id);
+            add_filter_container(data, name);
+        },
+        error: function(data, status, error) {
+            $('#loading_dialog').dialog('close');
+            alert('Filter could not be loaded, please try again.');
+        }
+    });
+}
+
+add_filter_container = function(data, title) {
+    // Adds elements which contain tests or test cases from the previously
+    // selected filter.
+
+    content = '<hr><div class="filter-title">' + title + '</div>';
+
+    if ($('#id_chart_type').val() == "pass/fail") {
+        test_label = "Tests";
+    } else {
+        test_label = "Test Cases";
+    }
+
+    content += '<div class="selector"><div class="selector-available"><h2>' +
+        'Select ' + test_label + '</h2>';
+
+    content += '<select id="available_tests" multiple class="filtered">';
+    for (i in data) {
+        if ($('#id_chart_type').val() == "pass/fail") {
+            content += '<option value="' + data[i].pk + '">' +
+                data[i].fields.test_id + '</option>';
+        } else {
+            content += '<option value="' + data[i].pk + '">' +
+                data[i].fields.test_case_id + '</option>';
+        }
+    }
+    content += '</select>';
+
+    content += '<a id="add_all_link" href="javascript: void(0)">' +
+        'Choose All</a>';
+    content += '</div>';
+
+    content += '<ul class="selector-chooser">' +
+        '<li><a href="javascript: void(0)" id="add_link"' +
+        'class="selector-add active"></a></li>' +
+        '<li><a href="javascript: void(0)" id="remove_link"' +
+        'class="selector-remove active"></a></li>' +
+        '</ul>';
+
+    content += '<div class="selector-chosen"><h2>' +
+        'Choosen ' + test_label + '</h2>';
+
+    content += '<select id="chosen_tests" onchange="toggle_alias()" multiple class="filtered"></select>';
+    content += '<a id="remove_all_link" href="javascript: void(0)">' +
+        'Remove All</a>';
+    content += '</div></div>';
+
+    content += '<div id="alias_container">Alias<br/>';
+    content += '<input type="text" onkeyup="copy_alias(this);" id="alias" />';
+    content += '</div>';
+
+    $('<div id="filter-container"></div>').html(
+        content).appendTo($('#filters_div'));
+
+    update_events();
+}
+
+update_events = function() {
+    // Add onclick events to the links controlling the select boxes.
+
+    $('#add_link').click(function() {
+        move_options('available_tests', 'chosen_tests');
+    });
+    $('#remove_link').click(function() {
+        move_options('chosen_tests', 'available_tests');
+    });
+    $('#add_all_link').click(function() {
+        $('#available_tests option').each(function() {
+            $(this).attr('selected', 'selected');
+        });
+        move_options('available_tests', 'chosen_tests');
+    });
+    $('#remove_all_link').click(function() {
+        $('#chosen_tests option').each(function() {
+            $(this).attr('selected', 'selected');
+        });
+        move_options('chosen_tests', 'available_tests');
+    });
+}
+
+move_options = function(from_element, to_element) {
+    var options = $("#" + from_element + " option:selected");
+    $("#" + to_element).append(options.clone());
+    $(options).remove();
+
+    update_aliases();
+    toggle_alias();
+}
+
+add_selected_options = function() {
+    // Adds options from chosen tests select box as hidden fields.
+
+    $('#chosen_tests option').each(function() {
+        if ($('#id_chart_type').val() == "pass/fail") {
+            field_name = "image_chart_tests";
+        } else {
+            field_name = "image_chart_test_cases";
+        }
+        $('<input type="hidden" name="' + field_name +
+          '" value="'+ $(this).val() + '" />').appendTo($('#add_filter_link'));
+    });
+}
+
+update_aliases = function() {
+    // Update hidden aliases inputs based on chosen tests.
+
+    $('#chosen_tests option').each(function() {
+        if ($('#alias_' + $(this).val()).length == 0) {
+            $('<input type="hidden" class="alias" data-sid="' + $(this).val() +
+              '" name="aliases" id="alias_' + $(this).val() +
+              '" />').appendTo($('#aliases_div'));
+        }
+    });
+    chosen_tests = $.map($('#chosen_tests option'), function(e) {
+        return e.value;
+    });
+    $('.alias').each(function(index, value) {
+        test_id = value.id.split('_')[1];
+
+        if (chosen_tests.indexOf(test_id) == -1) {
+            $('#alias_' + test_id).remove();
+        }
+    });
+}
+
+toggle_alias = function() {
+    // Show/hide alias input field.
+
+    if ($('#chosen_tests option:selected').length == 1) {
+        $('#alias_container').show();
+        test_id = $('#chosen_tests option:selected').val();
+        $('#alias').val($('#alias_' + test_id).val());
+    } else {
+        $('#alias_container').hide();
+    }
+}
+
+copy_alias = function(e) {
+    // Populate alias input based on the selected test.
+
+    if ($('#chosen_tests option:selected').length == 1) {
+        test_id = $('#chosen_tests option:selected').val();
+        $('#alias_' + test_id).val(e.value);
+    }
+}
+
+sort_aliases = function() {
+    // Pre submit function. Sort the aliases hidden inputs.
+
+    $('#aliases_div input').sort(function(a,b) {
+        return a.dataset.sid > b.dataset.sid;
+    }).appendTo('#aliases_div');
+}
+
+init_filter_dialog = function() {
+    // Setup the filter table dialog.
+
+    var filter_dialog = $('<div id="filter_select_dialog"></div>');
+    $('#all-filters_wrapper').wrapAll(filter_dialog);
+
+    $('#filter_select_dialog').dialog({
+        autoOpen: false,
+        title: 'Select Filter',
+        draggable: false,
+        height: 280,
+        width: 420,
+        modal: true,
+        resizable: false
+    });
+}
+
+init_loading_dialog = function() {
+    // Setup the loading image dialog.
+
+    $('#loading_dialog').dialog({
+        autoOpen: false,
+        title: '',
+        draggable: false,
+        height: 35,
+        width: 250,
+        modal: true,
+        resizable: false,
+        dialogClass: 'loading-dialog'
+    });
+
+    $('.loading-dialog div.ui-dialog-titlebar').hide();
+}

=== added file 'dashboard_app/templates/dashboard_app/image_chart_filter_form.html'
--- dashboard_app/templates/dashboard_app/image_chart_filter_form.html	1970-01-01 00:00:00 +0000
+++ dashboard_app/templates/dashboard_app/image_chart_filter_form.html	2013-09-12 13:30:08 +0000
@@ -0,0 +1,109 @@ 
+{% extends "dashboard_app/_content.html" %}
+{% load i18n %}
+{% load django_tables2 %}
+
+{% block extrahead %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/image-report-editor.js"></script>
+
+{% endblock %}
+
+
+{% block content %}
+<h1>Image Chart Filter</h1>
+
+{% block content_form %}
+<form action="" method="post">{% csrf_token %}
+
+  {% if form.errors %}
+  <div class="errors">
+    <div>
+      {{ form.non_field_errors }}
+      <ul>
+	{% for field in form %}
+	{% if field.errors %}
+        <li>{{ field.label }}: {{ field.errors|striptags }}</li>
+        {% endif %}
+	{% endfor %}
+      </ul>
+    </div>
+  </div>
+  {% endif %}
+
+  <div id="filters_div">
+    <div id="add_filter_link">
+      <a href="#" onclick="select_filter()">Select filter</a>
+      {{ form.filter }}
+      {{ form.image_chart }}
+      <input type="hidden" id="id_chart_type" value="{{ image_chart.chart_type }}"/>
+      {{ form.image_chart_tests }}
+      {{ form.image_chart_test_cases }}
+    </div>
+
+    <div>
+      {{ form.representation.label_tag }}
+      {{ form.representation }}
+    </div>
+  </div>
+
+  <div id="aliases_div">
+  </div>
+
+  <div class="submit-button">
+    <input type="submit" value="Save" />
+  </div>
+</form>
+
+{% endblock content_form %}
+
+{% render_table filters_table %}
+
+<div id="loading_dialog">
+<img src="{{ STATIC_URL }}dashboard_app/images/ajax-progress.gif" alt="Loading..." />
+</div>
+
+<script type="text/javascript">
+  $().ready(function () {
+
+    init_filter_dialog();
+    init_loading_dialog();
+
+    $('form').submit(function() {
+        add_selected_options();
+        sort_aliases();
+    });
+
+    {% if form.filter.value %}
+      filters_callback('{{ instance.filter.id }}',
+                       '{{ instance.filter.name }}');
+
+      if ($('#id_chart_type').val() == "pass/fail") {
+      {% for test in instance.imagecharttest_set.all %}
+        $('#available_tests option[value="{{ test.test_id }}"]').attr('selected', 'selected');
+      {% endfor %}
+      } else {
+      {% for test in instance.imagecharttestcase_set.all %}
+        $('#available_tests option[value="{{ test.test_case_id }}"]').attr('selected', 'selected');
+      {% endfor %}
+      }
+
+      move_options('available_tests', 'chosen_tests');
+
+      if ($('#id_chart_type').val() == "pass/fail") {
+      {% for test in instance.imagecharttest_set.all %}
+        $('#alias_{{ test.test_id }}').val('{{ test.name }}');
+      {% endfor %}
+      } else {
+      {% for test in instance.imagecharttestcase_set.all %}
+
+        $('#alias_{{ test.test_case_id }}').val('{{ test.name }}');
+      {% endfor %}
+      }
+
+    {% endif %}
+});
+
+</script>
+
+{% endblock %}

=== added file 'dashboard_app/templates/dashboard_app/image_report_chart_detail.html'
--- dashboard_app/templates/dashboard_app/image_report_chart_detail.html	1970-01-01 00:00:00 +0000
+++ dashboard_app/templates/dashboard_app/image_report_chart_detail.html	2013-09-12 19:32:47 +0000
@@ -0,0 +1,86 @@ 
+{% extends "dashboard_app/_content.html" %}
+{% load i18n %}
+
+{% block extrahead %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
+{% endblock %}
+
+{% block content %}
+
+<h1>Image Chart {{ image_chart.name }}</h1>
+
+
+<div class="fields-container">
+  <div class="form-field">
+    <a href="{{ image_chart.get_absolute_url }}/+edit">Edit</a> this chart.
+  </div>
+  <div class="form-field">
+    Description: {{ image_chart.description }}
+  </div>
+  <div class="form-field">
+    Chart type: {{ image_chart.chart_type }}
+  </div>
+  <div class="form-field">
+    Data table visible: {{ image_chart.is_data_table_visible }}
+  </div>
+  <div class="form-field">
+    Target goal: {{ image_chart.target_goal|floatformat:"-2" }}
+  </div>
+</div>
+
+
+<h3>Filters</h3>
+
+<div class="fields-container">
+  <div id="add_filter_link">
+    <a href="{{ image_chart.get_absolute_url }}/+add-filter">Add filter</a>
+  </div>
+</div>
+
+<div class="list-container">
+  {% for chart_filter in image_chart.imagechartfilter_set.all %}
+  <div class="chart-title">
+    {{ chart_filter.filter.name }}&nbsp;&nbsp;&nbsp;&nbsp;
+    <a style="font-size: 13px;" href="{{ chart_filter.get_absolute_url }}">
+      edit
+    </a>&nbsp;
+    <a style="font-size: 13px;" href="{{ chart_filter.get_absolute_url }}/+delete">
+      remove
+    </a>
+  </div>
+  <div>
+    {% if image_chart.chart_type == "pass/fail" %}
+      Tests:&nbsp;
+      {% for chart_test in chart_filter.imagecharttest_set.all %}
+        {% if forloop.last %}
+          {{ chart_test.test.test_id }}
+        {% else %}
+          {{ chart_test.test.test_id }},&nbsp;
+        {% endif %}
+      {% endfor %}
+    {% else %}
+      Test Cases:&nbsp
+      {% for chart_test in chart_filter.imagecharttestcase_set.all %}
+        {% if forloop.last %}
+          {{ chart_test.test_case.test_case_id }}
+        {% else %}
+          {{ chart_test.test_case.test_case_id }},&nbsp;
+        {% endif %}
+      {% endfor %}
+    {% endif %}
+  </div>
+  <div>
+    Representation: {{ chart_filter.representation }}
+  </div>
+
+  <hr/>
+  {% empty %}
+  <div>
+    <li>No filters added yet.</li>
+  </div>
+  {% endfor %}
+</div>
+
+
+{% endblock %}

=== added file 'dashboard_app/templates/dashboard_app/image_report_chart_form.html'
--- dashboard_app/templates/dashboard_app/image_report_chart_form.html	1970-01-01 00:00:00 +0000
+++ dashboard_app/templates/dashboard_app/image_report_chart_form.html	2013-09-12 14:12:07 +0000
@@ -0,0 +1,62 @@ 
+{% extends "dashboard_app/_content.html" %}
+{% load i18n %}
+{% load django_tables2 %}
+
+{% block extrahead %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
+
+{% endblock %}
+
+
+{% block content %}
+<h1>Add Image Charts 2.0</h1>
+
+{% block content_form %}
+<form action="" method="post">{% csrf_token %}
+
+  {% if form.errors %}
+  <div class="errors">
+    <div>
+      {{ form.non_field_errors }}
+      <ul>
+	{% for field in form %}
+	{% if field.errors %}
+        <li>{{ field.label }}: {{ field.errors|striptags }}</li>
+        {% endif %}
+	{% endfor %}
+      </ul>
+    </div>
+  </div>
+  {% endif %}
+
+  <div class="form-field">
+    {{ form.name.label_tag }}
+    {{ form.name }}
+    <input type="hidden" id="id_image_report" name="image_report" value="{{ image_report_id }}"/>
+  </div>
+  <div class="form-field">
+    {{ form.description.label_tag }}
+    {{ form.description }}
+  </div>
+  <div class="form-field">
+    {{ form.chart_type.label_tag }}
+    {{ form.chart_type }}
+  </div>
+  <div class="form-field">
+    {{ form.is_data_table_visible.label_tag }}
+    {{ form.is_data_table_visible }}
+  </div>
+  <div class="form-field">
+    {{ form.target_goal.label_tag }}
+    {{ form.target_goal }}
+  </div>
+
+  <div class="submit-button">
+    <input type="submit" value="Save" />
+  </div>
+</form>
+
+{% endblock content_form %}
+
+{% endblock %}

=== added file 'dashboard_app/templates/dashboard_app/image_report_detail.html'
--- dashboard_app/templates/dashboard_app/image_report_detail.html	1970-01-01 00:00:00 +0000
+++ dashboard_app/templates/dashboard_app/image_report_detail.html	2013-09-12 14:12:07 +0000
@@ -0,0 +1,80 @@ 
+{% extends "dashboard_app/_content.html" %}
+{% load i18n %}
+
+{% block extrahead %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
+{% endblock %}
+
+{% block content %}
+
+<h1>Image Report {{ image_report.name }}</h1>
+
+<div class="fields-container">
+  <div class="form-field">
+    Status&#58;&nbsp;
+    {% if image_report.is_published %}
+    <span style="font-weight: bold; color: green;">
+      Published
+    </span>
+    {% else %}
+    <span style="font-weight: bold; color: orange;">
+      Not Published
+    </span>
+    {% endif %}
+  </div>
+  <div class="form-field">
+    Description&#58;&nbsp;{{ image_report.description }}
+  </div>
+  <div class="form-field">
+    <a href="{{ image_report.get_absolute_url }}/+edit">Edit</a> this image report.
+  </div>
+  <div class="form-field">
+    {% if image_report.is_published %}
+    <a href="{{ image_report.get_absolute_url }}/+unpublish">Unpublish</a> this image report.
+    {% else %}
+    <a href="{{ image_report.get_absolute_url }}/+publish">Publish</a> this image report.
+{% endif %}
+  </div>
+</div>
+
+<h3>Charts</h3>
+
+<div class="fields-container">
+  <a href="{% url dashboard_app.views.image_reports.views.image_chart_add %}?image_report_id={{ image_report.id }}">
+    Add new chart
+  </a>
+</div>
+
+<div class="list-container">
+  {% for image_chart in image_report.imagereportchart_set.all %}
+  <div class="chart-title">
+    {{ image_chart.name }}&nbsp;&nbsp;
+    <a style="font-size: 13px;" href="{{ image_chart.get_absolute_url }}">
+      details
+    </a>&nbsp;
+    <a style="font-size: 13px;" href="{{ image_chart.get_absolute_url }}">
+      preview
+    </a>
+  </div>
+  <div>
+    Description: {{ image_chart.description }}
+  </div>
+  <div>
+    Chart type: {{ image_chart.chart_type }}
+  </div>
+  <div>
+    Data table visible: {{ image_chart.is_data_table_visible }}
+  </div>
+  <div>
+    Target goal: {{ image_chart.target_goal|floatformat:"-2" }}
+  </div>
+  <hr/>
+  {% empty %}
+  <div>
+    <li>No charts added yet.</li>
+  </div>
+  {% endfor %}
+</div>
+
+{% endblock %}

=== added file 'dashboard_app/templates/dashboard_app/image_report_form.html'
--- dashboard_app/templates/dashboard_app/image_report_form.html	1970-01-01 00:00:00 +0000
+++ dashboard_app/templates/dashboard_app/image_report_form.html	2013-09-12 14:12:07 +0000
@@ -0,0 +1,45 @@ 
+{% extends "dashboard_app/_content.html" %}
+
+{% block extrahead %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
+{% endblock %}
+
+{% block content %}
+<h1>Image Reports 2.0</h1>
+
+{% block content_form %}
+<form action="" method="post">{% csrf_token %}
+
+  {% if form.errors %}
+  <div class="errors">
+    <div>
+      {{ form.non_field_errors }}
+      <ul>
+	{% for field in form %}
+	{% if field.errors %}
+        <li>{{ field.label }}: {{ field.errors|striptags }}</li>
+        {% endif %}
+	{% endfor %}
+      </ul>
+    </div>
+  </div>
+  {% endif %}
+
+<div class="form-field">
+  {{ form.name.label_tag }}
+  {{ form.name }}
+</div>
+<div class="form-field">
+  {{ form.description.label_tag }}
+  {{ form.description }}
+</div>
+
+<div class="submit-button">
+<input type="submit" value="Save" />
+</div>
+</form>
+
+{% endblock content_form %}
+
+{% endblock %}

=== added file 'dashboard_app/templates/dashboard_app/image_report_list.html'
--- dashboard_app/templates/dashboard_app/image_report_list.html	1970-01-01 00:00:00 +0000
+++ dashboard_app/templates/dashboard_app/image_report_list.html	2013-09-11 22:18:16 +0000
@@ -0,0 +1,37 @@ 
+{% extends "dashboard_app/_content.html" %}
+
+{% block extrahead %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
+{% endblock %}
+
+{% block content %}
+<h1>Image Reports 2.0</h1>
+
+<p style="margin-left: 10px;">
+  <a href="{% url dashboard_app.views.image_reports.views.image_report_add %}">
+    Add new Image Report
+  </a>
+</p>
+
+{% for image_report in image_reports %}
+<div class="list-container">
+  <div style="float: left;">
+    <a href="{{ image_report.get_absolute_url }}">{{ image_report.name }}</a>
+    &nbsp;&nbsp;
+  </div>
+  {% if image_report.is_published %}
+  <div style="font-weight: bold; float: right; color: green;">
+    Published
+  </div>
+  {% else %}
+  <div style="font-weight: bold; float: right; color: orange;">
+    Not Published
+  </div>
+  {% endif %}
+  <div style="clear: both;">
+    {{ image_report.description }}
+  </div>
+</div>
+{% endfor %}
+{% endblock %}

=== modified file 'dashboard_app/urls.py'
--- dashboard_app/urls.py	2013-09-05 11:27:15 +0000
+++ dashboard_app/urls.py	2013-09-13 14:16:05 +0000
@@ -33,6 +33,8 @@ 
     url(r'^filters/\+add$', 'filters.views.filter_add'),
     url(r'^filters/\+add-preview-json$', 'filters.views.filter_preview_json'),
     url(r'^filters/\+add-cases-for-test-json$', 'filters.views.filter_add_cases_for_test_json'),
+    url(r'^filters/\+get-tests-json$', 'filters.views.get_tests_json'),
+    url(r'^filters/\+get-test-cases-json$', 'filters.views.get_test_cases_json'),
     url(r'^filters/\+attribute-name-completion-json$', 'filters.views.filter_attr_name_completion_json'),
     url(r'^filters/\+attribute-value-completion-json$', 'filters.views.filter_attr_value_completion_json'),
     url(r'^filters/~(?P<username>[^/]+)/(?P<name>[a-zA-Z0-9-_]+)$', 'filters.views.filter_detail'),
@@ -63,6 +65,18 @@ 
     url(r'^permalink/bundle/(?P<content_sha1>[0-9a-z]+)/$', 'redirect_to_bundle'),
     url(r'^permalink/bundle/(?P<content_sha1>[0-9a-z]+)/(?P<trailing>.*)$', 'redirect_to_bundle'),
     url(r'^image-reports/$', 'images.image_report_list'),
+    url(r'^image-charts/$', 'image_reports.views.image_report_list'),
+    url(r'^image-charts/(?P<name>[a-zA-Z0-9-_]+)$', 'image_reports.views.image_report_detail'),
+    url(r'^image-charts/\+add$', 'image_reports.views.image_report_add'),
+    url(r'^image-charts/(?P<name>[a-zA-Z0-9-_]+)/\+edit$', 'image_reports.views.image_report_edit'),
+    url(r'^image-charts/(?P<name>[a-zA-Z0-9-_]+)/\+publish$', 'image_reports.views.image_report_publish'),
+    url(r'^image-charts/(?P<name>[a-zA-Z0-9-_]+)/\+unpublish$', 'image_reports.views.image_report_unpublish'),
+    url(r'^image-chart/(?P<id>[a-zA-Z0-9-_]+)$', 'image_reports.views.image_chart_detail'),
+    url(r'^image-chart/\+add$', 'image_reports.views.image_chart_add'),
+    url(r'^image-chart/(?P<id>[a-zA-Z0-9-_]+)/\+edit$', 'image_reports.views.image_chart_edit'),
+    url(r'^image-chart/(?P<id>[a-zA-Z0-9-_]+)/\+add-filter$', 'image_reports.views.image_chart_filter_add'),
+    url(r'^image-chart-filter/(?P<id>[a-zA-Z0-9-_]+)$', 'image_reports.views.image_chart_filter_edit'),
+    url(r'^image-chart-filter/(?P<id>[a-zA-Z0-9-_]+)/\+delete$', 'image_reports.views.image_chart_filter_delete'),
     url(r'^pmqa$', 'pmqa.pmqa_view'),
     url(r'^pmqa(?P<pathname>/[a-zA-Z0-9/._-]+/)(?P<device_type>[a-zA-Z0-9-_]+)$', 'pmqa.pmqa_filter_view'),
     url(r'^pmqa(?P<pathname>/[a-zA-Z0-9/._-]+/)(?P<device_type>[a-zA-Z0-9-_]+)/json$', 'pmqa.pmqa_filter_view_json'),

=== modified file 'dashboard_app/views/filters/tables.py'
--- dashboard_app/views/filters/tables.py	2013-01-10 01:56:51 +0000
+++ dashboard_app/views/filters/tables.py	2013-09-09 11:47:00 +0000
@@ -109,6 +109,15 @@ 
         return TestRunFilter.objects.filter(public=True)
 
 
+class AllFiltersSimpleTable(DataTablesTable):
+
+    name = TemplateColumn('''
+    <a href="#" onclick="filters_callback('{{ record.id }}', '{{ record.name }}');">{{ record.name }}</a>
+    ''')
+
+    def get_queryset(self):
+        return TestRunFilter.objects.all()
+
 
 class TestRunColumn(Column):
     def render(self, record):

=== modified file 'dashboard_app/views/filters/views.py'
--- dashboard_app/views/filters/views.py	2013-01-10 01:56:51 +0000
+++ dashboard_app/views/filters/views.py	2013-09-09 11:47:00 +0000
@@ -20,6 +20,7 @@ 
 
 from django.contrib.auth.decorators import login_required
 from django.contrib.contenttypes.models import ContentType
+from django.core import serializers
 from django.core.exceptions import PermissionDenied, ValidationError
 from django.core.urlresolvers import reverse
 from django.http import HttpResponse, HttpResponseRedirect
@@ -37,6 +38,7 @@ 
     evaluate_filter,
     )
 from dashboard_app.models import (
+    Bundle,
     NamedAttribute,
     Test,
     TestCase,
@@ -234,6 +236,24 @@ 
         mimetype='application/json')
 
 
+def get_tests_json(request):
+
+    tests = Test.objects.filter(
+        test_runs__bundle__bundle_stream__testrunfilter__id=request.GET['id']).distinct()
+
+    data = serializers.serialize('json', tests)
+    return HttpResponse(data, mimetype='application/json')
+
+
+def get_test_cases_json(request):
+
+    test_cases = TestCase.objects.filter(
+        test__test_runs__bundle__bundle_stream__testrunfilter__id=request.GET['id']).distinct()
+
+    data = serializers.serialize('json', test_cases)
+    return HttpResponse(data, mimetype='application/json')
+
+
 def filter_attr_name_completion_json(request):
     term = request.GET['term']
     content_type_id = ContentType.objects.get_for_model(TestRun).id

=== added directory 'dashboard_app/views/image_reports'
=== added file 'dashboard_app/views/image_reports/__init__.py'
--- dashboard_app/views/image_reports/__init__.py	1970-01-01 00:00:00 +0000
+++ dashboard_app/views/image_reports/__init__.py	2013-09-03 15:03:06 +0000
@@ -0,0 +1,17 @@ 
+# Copyright (C) 2010-2013 Linaro Limited
+#
+# Author: Stevan Radakovic <stevan.radakovic@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/>.

=== added file 'dashboard_app/views/image_reports/forms.py'
--- dashboard_app/views/image_reports/forms.py	1970-01-01 00:00:00 +0000
+++ dashboard_app/views/image_reports/forms.py	2013-09-13 12:59:23 +0000
@@ -0,0 +1,91 @@ 
+# Copyright (C) 2010-2013 Linaro Limited
+#
+# Author: Stevan Radakovic <stevan.radakovic@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 import forms
+
+from dashboard_app.models import (
+    ImageReport,
+    ImageReportChart,
+    ImageChartFilter,
+    ImageChartTest,
+    ImageChartTestCase,
+    Test,
+    TestCase,
+)
+
+
+class ImageReportEditorForm(forms.ModelForm):
+    class Meta:
+        model = ImageReport
+        exclude = ('owner', 'is_published',)
+
+    def save(self, commit=True, **kwargs):
+        instance = super(ImageReportEditorForm,
+                         self).save(commit=commit, **kwargs)
+        return instance
+
+    def is_valid(self):
+        return super(ImageReportEditorForm, self).is_valid()
+
+    def full_clean(self):
+        super(ImageReportEditorForm, self).full_clean()
+
+    def __init__(self, user, *args, **kwargs):
+        super(ImageReportEditorForm, self).__init__(*args, **kwargs)
+
+
+class ImageReportChartForm(forms.ModelForm):
+    class Meta:
+        model = ImageReportChart
+        widgets = {'image_report': forms.HiddenInput}
+
+    def __init__(self, user, *args, **kwargs):
+        super(ImageReportChartForm, self).__init__(*args, **kwargs)
+        if len(self.instance.imagechartfilter_set.all()) != 0:
+            self.fields['chart_type'].label = ""
+            self.fields['chart_type'].widget = forms.HiddenInput()
+
+    def save(self, commit=True, **kwargs):
+        instance = super(ImageReportChartForm,
+                         self).save(commit=commit, **kwargs)
+        return instance
+
+
+class ImageChartFilterForm(forms.ModelForm):
+
+    image_chart_tests = forms.ModelMultipleChoiceField(
+        widget=forms.MultipleHiddenInput,
+        queryset=Test.objects.all().order_by("id"),
+        required=False)
+    image_chart_test_cases = forms.ModelMultipleChoiceField(
+        widget=forms.MultipleHiddenInput,
+        queryset=TestCase.objects.all().order_by("id"),
+        required=False)
+
+    class Meta:
+        model = ImageChartFilter
+        widgets = {'filter': forms.HiddenInput,
+                   'image_chart': forms.HiddenInput,}
+
+    def __init__(self, user, *args, **kwargs):
+        super(ImageChartFilterForm, self).__init__(*args, **kwargs)
+
+    def save(self, commit=True, **kwargs):
+        instance = super(ImageChartFilterForm,
+                         self).save(commit=commit, **kwargs)
+        return instance

=== added file 'dashboard_app/views/image_reports/views.py'
--- dashboard_app/views/image_reports/views.py	1970-01-01 00:00:00 +0000
+++ dashboard_app/views/image_reports/views.py	2013-09-13 12:59:23 +0000
@@ -0,0 +1,325 @@ 
+# Copyright (C) 2010-2013 Linaro Limited
+#
+# Author: Stevan Radakovic <stevan.radakovic@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/>.
+
+import json
+
+from django.contrib.auth.decorators import login_required
+from django.core.exceptions import PermissionDenied, ValidationError
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.safestring import mark_safe
+
+from lava_server.bread_crumbs import (
+    BreadCrumb,
+    BreadCrumbTrail,
+)
+
+from dashboard_app.views import index
+
+from dashboard_app.views.image_reports.forms import (
+    ImageReportEditorForm,
+    ImageReportChartForm,
+    ImageChartFilterForm,
+    )
+
+from dashboard_app.models import (
+    ImageReport,
+    ImageReportChart,
+    ImageChartFilter,
+    ImageChartTest,
+    ImageChartTestCase,
+    Test,
+    TestCase,
+    TestRunFilter,
+    )
+
+from dashboard_app.views.filters.tables import AllFiltersSimpleTable
+
+
+
+@BreadCrumb("Image reports", parent=index)
+def image_report_list(request):
+
+    if request.user.is_authenticated():
+        image_reports = ImageReport.objects.all()
+    else:
+        image_reports = None
+
+    return render_to_response(
+        'dashboard_app/image_report_list.html', {
+            "image_reports": image_reports,
+        }, RequestContext(request)
+    )
+
+@BreadCrumb("Image report {name}", parent=image_report_list, needs=['name'])
+def image_report_detail(request, name):
+    image_report = ImageReport.objects.get(name=name)
+
+    return render_to_response(
+        'dashboard_app/image_report_detail.html', {
+            'image_report': image_report,
+            'bread_crumb_trail': BreadCrumbTrail.leading_to(
+                image_report_detail, name=name),
+        }, RequestContext(request)
+    )
+
+@BreadCrumb("Add new image report", parent=image_report_list)
+@login_required
+def image_report_add(request):
+    return image_report_form(
+        request,
+        BreadCrumbTrail.leading_to(image_report_add))
+
+@BreadCrumb("Update image report {name}", parent=image_report_list,
+            needs=['name'])
+@login_required
+def image_report_edit(request, name):
+    image_report = ImageReport.objects.get(name=name)
+    return image_report_form(
+        request,
+        BreadCrumbTrail.leading_to(image_report_edit,
+                                   name=name),
+        instance=image_report)
+
+@BreadCrumb("Publish image report {name}", parent=image_report_list,
+            needs=['name'])
+@login_required
+def image_report_publish(request, name):
+    image_report = ImageReport.objects.get(name=name)
+    image_report.is_published = True
+    image_report.save()
+
+    return render_to_response(
+        'dashboard_app/image_report_detail.html', {
+            'image_report': image_report,
+            'bread_crumb_trail': BreadCrumbTrail.leading_to(
+                image_report_detail, name=name),
+        }, RequestContext(request)
+    )
+
+@BreadCrumb("Unpublish image report {name}", parent=image_report_list,
+            needs=['name'])
+@login_required
+def image_report_unpublish(request, name):
+    image_report = ImageReport.objects.get(name=name)
+    image_report.is_published = False
+    image_report.save()
+
+    return render_to_response(
+        'dashboard_app/image_report_detail.html', {
+            'image_report': image_report,
+            'bread_crumb_trail': BreadCrumbTrail.leading_to(
+                image_report_detail, name=name),
+        }, RequestContext(request)
+    )
+
+def image_report_form(request, bread_crumb_trail, instance=None):
+
+    if request.method == 'POST':
+
+        form = ImageReportEditorForm(request.user, request.POST,
+                                     instance=instance)
+        if form.is_valid():
+            image_report = form.save()
+            return HttpResponseRedirect(image_report.get_absolute_url())
+
+    else:
+        form = ImageReportEditorForm(request.user, instance=instance)
+
+    return render_to_response(
+        'dashboard_app/image_report_form.html', {
+            'bread_crumb_trail': bread_crumb_trail,
+            'form': form,
+        }, RequestContext(request))
+
+@BreadCrumb("Image chart details", parent=image_report_list)
+def image_chart_detail(request, id):
+    image_chart = ImageReportChart.objects.get(id=id)
+
+    return render_to_response(
+        'dashboard_app/image_report_chart_detail.html', {
+            'image_chart': image_chart,
+            'bread_crumb_trail': BreadCrumbTrail.leading_to(
+                image_chart_detail, id=id),
+        }, RequestContext(request)
+    )
+
+@BreadCrumb("Add new image chart", parent=image_report_list)
+@login_required
+def image_chart_add(request):
+    return image_chart_form(
+        request,
+        BreadCrumbTrail.leading_to(image_chart_add))
+
+@BreadCrumb("Update image chart", parent=image_report_list)
+@login_required
+def image_chart_edit(request, id):
+    image_chart = ImageReportChart.objects.get(id=id)
+    return image_chart_form(
+        request,
+        BreadCrumbTrail.leading_to(image_chart_edit,
+                                   id=id),
+        instance=image_chart)
+
+def image_chart_form(request, bread_crumb_trail, instance=None):
+
+    if request.method == 'POST':
+
+        form = ImageReportChartForm(request.user, request.POST,
+                                    instance=instance)
+        if form.is_valid():
+            image_chart = form.save()
+            return HttpResponseRedirect(
+                image_chart.get_absolute_url())
+
+    else:
+        form = ImageReportChartForm(request.user, instance=instance)
+
+    if not instance:
+        image_report_id = request.GET.get('image_report_id', None)
+    else:
+        image_report_id = instance.image_report.id
+
+    filters_table = AllFiltersSimpleTable("all-filters", None)
+
+    return render_to_response(
+        'dashboard_app/image_report_chart_form.html', {
+            'bread_crumb_trail': bread_crumb_trail,
+            'form': form,
+            'filters_table': filters_table,
+            'image_report_id': image_report_id,
+        }, RequestContext(request))
+
+@BreadCrumb("Image chart add filter", parent=image_report_list)
+def image_chart_filter_add(request, id):
+    image_chart = ImageReportChart.objects.get(id=id)
+    return image_chart_filter_form(
+        request,
+        BreadCrumbTrail.leading_to(image_chart_filter_add),
+        chart_instance=image_chart)
+
+@BreadCrumb("Update image chart filter", parent=image_report_list)
+@login_required
+def image_chart_filter_edit(request, id):
+    image_chart_filter = ImageChartFilter.objects.get(id=id)
+    return image_chart_filter_form(
+        request,
+        BreadCrumbTrail.leading_to(image_chart_filter_edit, id=id),
+        instance=image_chart_filter)
+
+@BreadCrumb("Image chart add filter", parent=image_report_list)
+def image_chart_filter_delete(request, id):
+    image_chart_filter = ImageChartFilter.objects.get(id=id)
+    url = image_chart_filter.image_chart.get_absolute_url()
+    image_chart_filter.delete()
+    return HttpResponseRedirect(url)
+
+def image_chart_filter_form(request, bread_crumb_trail, chart_instance=None,
+                            instance=None):
+
+    if instance:
+        chart_instance = instance.image_chart
+
+    if request.method == 'POST':
+
+        form = ImageChartFilterForm(request.user, request.POST,
+                                    instance=instance)
+
+        if form.is_valid():
+
+            chart_filter = form.save()
+            aliases = request.POST.getlist('aliases')
+
+
+            if chart_filter.image_chart.chart_type == 'pass/fail':
+
+                image_chart_tests = Test.objects.filter(
+                    imagecharttest__image_chart_filter=chart_filter).order_by(
+                        'id')
+
+                tests = form.cleaned_data['image_chart_tests']
+
+                for index, test in enumerate(tests):
+                    if test in image_chart_tests:
+                        chart_test = ImageChartTest.objects.get(
+                            image_chart_filter=chart_filter, test=test)
+                        chart_test.name = aliases[index]
+                        chart_test.save()
+                    else:
+                        chart_test = ImageChartTest()
+                        chart_test.image_chart_filter = chart_filter
+                        chart_test.test = test
+                        chart_test.name = aliases[index]
+                        chart_test.save()
+
+                for index, chart_test in enumerate(image_chart_tests):
+                    if chart_test not in tests:
+                        ImageChartTest.objects.get(
+                            image_chart_filter=chart_filter,
+                            test=chart_test).delete()
+
+                return HttpResponseRedirect(
+                    chart_filter.image_chart.get_absolute_url())
+
+            else:
+
+                image_chart_test_cases = TestCase.objects.filter(
+                    imagecharttestcase__image_chart_filter=
+                    chart_filter).order_by('id')
+
+                test_cases = form.cleaned_data['image_chart_test_cases']
+
+                for index, test_case in enumerate(test_cases):
+                    if test_case in image_chart_test_cases:
+                        chart_test_case = ImageChartTestCase.objects.get(
+                            image_chart_filter=chart_filter,
+                            test_case=test_case)
+                        chart_test_case.name = aliases[index]
+                        chart_test_case.save()
+                    else:
+                        chart_test_case = ImageChartTestCase()
+                        chart_test_case.image_chart_filter = chart_filter
+                        chart_test_case.test_case = test_case
+                        chart_test_case.name = aliases[index]
+                        chart_test_case.save()
+
+                for index, chart_test_case in enumerate(
+                        image_chart_test_cases):
+                    if chart_test_case not in test_cases:
+                        ImageChartTestCase.objects.get(
+                            image_chart_filter=chart_filter,
+                            test_case=chart_test_case).delete()
+
+                return HttpResponseRedirect(
+                    chart_filter.image_chart.get_absolute_url())
+
+    else:
+        form = ImageChartFilterForm(request.user, instance=instance,
+                                    initial={'image_chart': chart_instance})
+
+    filters_table = AllFiltersSimpleTable("all-filters", None)
+
+    return render_to_response(
+        'dashboard_app/image_chart_filter_form.html', {
+            'bread_crumb_trail': bread_crumb_trail,
+            'filters_table': filters_table,
+            'image_chart': chart_instance,
+            'instance': instance,
+            'form': form,
+        }, RequestContext(request))