Merge lp:~stevanr/lava-dashboard/image-report-editor-frontend into lp:lava-dashboard

Proposed by Stevan Radaković on 2013-09-12
Status: Merged
Merged at revision: 422
Proposed branch: lp:~stevanr/lava-dashboard/image-report-editor-frontend
Merge into: lp:lava-dashboard
Diff against target: 1948 lines (+1720/-95)
16 files modified
dashboard_app/migrations/0031_auto__del_imagecharttestrun__add_imagecharttest__add_unique_imagechart.py (+388/-0)
dashboard_app/models.py (+134/-95)
dashboard_app/static/dashboard_app/css/image-charts.css (+82/-0)
dashboard_app/static/dashboard_app/js/image-report-editor.js (+221/-0)
dashboard_app/templates/dashboard_app/image_chart_filter_form.html (+109/-0)
dashboard_app/templates/dashboard_app/image_report_chart_detail.html (+86/-0)
dashboard_app/templates/dashboard_app/image_report_chart_form.html (+62/-0)
dashboard_app/templates/dashboard_app/image_report_detail.html (+80/-0)
dashboard_app/templates/dashboard_app/image_report_form.html (+45/-0)
dashboard_app/templates/dashboard_app/image_report_list.html (+37/-0)
dashboard_app/urls.py (+14/-0)
dashboard_app/views/filters/tables.py (+9/-0)
dashboard_app/views/filters/views.py (+20/-0)
dashboard_app/views/image_reports/__init__.py (+17/-0)
dashboard_app/views/image_reports/forms.py (+91/-0)
dashboard_app/views/image_reports/views.py (+325/-0)
To merge this branch: bzr merge lp:~stevanr/lava-dashboard/image-report-editor-frontend
Reviewer Review Type Date Requested Status
Antonio Terceiro 2013-09-12 Approve on 2013-09-13
Review via email: mp+185255@code.launchpad.net

Description of the change

To post a comment you must log in.
432. By Stevan Radaković on 2013-09-12

Fix ordering in aliases bug.

433. By Stevan Radaković on 2013-09-12

Small makeup changes.

434. By Stevan Radaković on 2013-09-12

Add comments to js file.

435. By Stevan Radaković on 2013-09-12

Remove empty options from select boxes.

436. By Stevan Radaković on 2013-09-12

Set redirect for image report chart add/edit view.

437. By Stevan Radaković on 2013-09-12

Chart type not editable when filters are present.

438. By Stevan Radaković on 2013-09-12

Fix aliases for test cases.

Antonio Terceiro (terceiro) wrote :

> === added file 'dashboard_app/migrations/0030_auto__add_imagecharttest__add_imagereport__add_imagecharttestcase__add.py'
> === removed file 'dashboard_app/migrations/0030_auto__add_imagecharttestcase__add_imagereport__add_imagecharttestrun__.py'

AFAICT you cannot remove migrations that were already added and were already
run, only add new migrations. I think you have to put the old one back, remove
the new one, and generate one that will record just the changes since the
previous.

Did you test upgrading from trunk code to your new version?

 review needs-fixing

review: Needs Fixing
439. By Stevan Radaković on 2013-09-13

Revert migration delete. Add new migration step.

Stevan Radaković (stevanr) wrote :

Of course you can, the only thing you have to do is something like:
"lava-server manage migrate dashboard_app 0029"
before the migration.

I realize our scripts on staging and production are not that sophisticated, so I reverted the deleted migration and created new one based on that migration step and my changes to the model.

440. By Stevan Radaković on 2013-09-13

Change database yet again.

441. By Stevan Radaković on 2013-09-13

Better way to update tests/test cases within filter.

442. By Stevan Radaković on 2013-09-13

Remove item menu for now.

Antonio Terceiro (terceiro) wrote :

On Fri, Sep 13, 2013 at 10:19:41AM -0000, Stevan Radaković wrote:
> Of course you can, the only thing you have to do is something like:
> "lava-server manage migrate dashboard_app 0029" before the migration.

Sure, but that defeats the point of using migrations since it will not
require specific manual steps on a specific upgrade.

> I realize our scripts on staging and production are not that
> sophisticated, so I reverted the deleted migration and created new one
> based on that migration step and my changes to the model.

The point of migrations is to have your database evolve without manual
intervention other than "upgrade my database", and having this "upgrade
my datavase" action always to the right thing.

 review approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'dashboard_app/migrations/0031_auto__del_imagecharttestrun__add_imagecharttest__add_unique_imagechart.py'
2--- dashboard_app/migrations/0031_auto__del_imagecharttestrun__add_imagecharttest__add_unique_imagechart.py 1970-01-01 00:00:00 +0000
3+++ dashboard_app/migrations/0031_auto__del_imagecharttestrun__add_imagecharttest__add_unique_imagechart.py 2013-09-13 13:13:49 +0000
4@@ -0,0 +1,388 @@
5+# -*- coding: utf-8 -*-
6+import datetime
7+from south.db import db
8+from south.v2 import SchemaMigration
9+from django.db import models
10+
11+
12+class Migration(SchemaMigration):
13+
14+ def forwards(self, orm):
15+ # Deleting model 'ImageChartTestRun'
16+ db.delete_table('dashboard_app_imagecharttestrun')
17+
18+ # Adding model 'ImageChartTest'
19+ db.create_table('dashboard_app_imagecharttest', (
20+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
21+ ('image_chart_filter', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.ImageChartFilter'])),
22+ ('test', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.Test'])),
23+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
24+ ))
25+ db.send_create_signal('dashboard_app', ['ImageChartTest'])
26+
27+ # Adding unique constraint on 'ImageChartTest', fields ['image_chart_filter', 'test']
28+ db.create_unique('dashboard_app_imagecharttest', ['image_chart_filter_id', 'test_id'])
29+
30+ # Adding model 'ImageChartFilter'
31+ db.create_table('dashboard_app_imagechartfilter', (
32+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
33+ ('image_chart', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.ImageReportChart'])),
34+ ('filter', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.TestRunFilter'], null=True, on_delete=models.SET_NULL)),
35+ ('representation', self.gf('django.db.models.fields.CharField')(default='lines', max_length=20)),
36+ ))
37+ db.send_create_signal('dashboard_app', ['ImageChartFilter'])
38+
39+ # Deleting field 'ImageChartTestCase.image_chart'
40+ db.delete_column('dashboard_app_imagecharttestcase', 'image_chart_id')
41+
42+ # Adding field 'ImageChartTestCase.image_chart_filter'
43+ db.add_column('dashboard_app_imagecharttestcase', 'image_chart_filter',
44+ self.gf('django.db.models.fields.related.ForeignKey')(default=0, to=orm['dashboard_app.ImageChartFilter']),
45+ keep_default=False)
46+
47+ # Adding unique constraint on 'ImageChartTestCase', fields ['image_chart_filter', 'test_case']
48+ db.create_unique('dashboard_app_imagecharttestcase', ['image_chart_filter_id', 'test_case_id'])
49+
50+ # Adding field 'ImageReport.is_published'
51+ db.add_column('dashboard_app_imagereport', 'is_published',
52+ self.gf('django.db.models.fields.BooleanField')(default=False),
53+ keep_default=False)
54+
55+ # Deleting field 'ImageReportChart.representation'
56+ db.delete_column('dashboard_app_imagereportchart', 'representation')
57+
58+ # Adding field 'ImageReportChart.description'
59+ db.add_column('dashboard_app_imagereportchart', 'description',
60+ self.gf('django.db.models.fields.TextField')(null=True, blank=True),
61+ keep_default=False)
62+
63+
64+ def backwards(self, orm):
65+ # Removing unique constraint on 'ImageChartTestCase', fields ['image_chart_filter', 'test_case']
66+ db.delete_unique('dashboard_app_imagecharttestcase', ['image_chart_filter_id', 'test_case_id'])
67+
68+ # Removing unique constraint on 'ImageChartTest', fields ['image_chart_filter', 'test']
69+ db.delete_unique('dashboard_app_imagecharttest', ['image_chart_filter_id', 'test_id'])
70+
71+ # Adding model 'ImageChartTestRun'
72+ db.create_table('dashboard_app_imagecharttestrun', (
73+ ('test_run', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.TestRun'])),
74+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
75+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
76+ ('image_chart', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.ImageReportChart'])),
77+ ))
78+ db.send_create_signal('dashboard_app', ['ImageChartTestRun'])
79+
80+ # Deleting model 'ImageChartTest'
81+ db.delete_table('dashboard_app_imagecharttest')
82+
83+ # Deleting model 'ImageChartFilter'
84+ db.delete_table('dashboard_app_imagechartfilter')
85+
86+ # Adding field 'ImageChartTestCase.image_chart'
87+ db.add_column('dashboard_app_imagecharttestcase', 'image_chart',
88+ self.gf('django.db.models.fields.related.ForeignKey')(default=0, to=orm['dashboard_app.ImageReportChart']),
89+ keep_default=False)
90+
91+ # Deleting field 'ImageChartTestCase.image_chart_filter'
92+ db.delete_column('dashboard_app_imagecharttestcase', 'image_chart_filter_id')
93+
94+ # Deleting field 'ImageReport.is_published'
95+ db.delete_column('dashboard_app_imagereport', 'is_published')
96+
97+ # Adding field 'ImageReportChart.representation'
98+ db.add_column('dashboard_app_imagereportchart', 'representation',
99+ self.gf('django.db.models.fields.CharField')(default='pass/fail', max_length=20),
100+ keep_default=False)
101+
102+ # Deleting field 'ImageReportChart.description'
103+ db.delete_column('dashboard_app_imagereportchart', 'description')
104+
105+
106+ models = {
107+ 'auth.group': {
108+ 'Meta': {'object_name': 'Group'},
109+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
110+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
111+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
112+ },
113+ 'auth.permission': {
114+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
115+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
116+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
117+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
118+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
119+ },
120+ 'auth.user': {
121+ 'Meta': {'object_name': 'User'},
122+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
123+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
124+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
125+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
126+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
127+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
128+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
129+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
130+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
131+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
132+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
133+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
134+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
135+ },
136+ 'contenttypes.contenttype': {
137+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
138+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
139+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
140+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
141+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
142+ },
143+ 'dashboard_app.attachment': {
144+ 'Meta': {'object_name': 'Attachment'},
145+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
146+ 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
147+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
148+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
149+ 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
150+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
151+ 'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'})
152+ },
153+ 'dashboard_app.bundle': {
154+ 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
155+ '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
156+ '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
157+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
158+ 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
159+ 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
160+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
161+ 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
162+ 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
163+ 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
164+ },
165+ 'dashboard_app.bundledeserializationerror': {
166+ 'Meta': {'object_name': 'BundleDeserializationError'},
167+ 'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}),
168+ 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
169+ 'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'})
170+ },
171+ 'dashboard_app.bundlestream': {
172+ 'Meta': {'object_name': 'BundleStream'},
173+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
174+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
175+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
176+ 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
177+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
178+ 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
179+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
180+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
181+ },
182+ 'dashboard_app.hardwaredevice': {
183+ 'Meta': {'object_name': 'HardwareDevice'},
184+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
185+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
186+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
187+ },
188+ 'dashboard_app.image': {
189+ 'Meta': {'object_name': 'Image'},
190+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['dashboard_app.TestRunFilter']"}),
191+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
192+ 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'})
193+ },
194+ 'dashboard_app.imagechartfilter': {
195+ 'Meta': {'object_name': 'ImageChartFilter'},
196+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
197+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
198+ 'image_chart': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.ImageReportChart']"}),
199+ 'representation': ('django.db.models.fields.CharField', [], {'default': "'lines'", 'max_length': '20'})
200+ },
201+ 'dashboard_app.imagecharttest': {
202+ 'Meta': {'unique_together': "(('image_chart_filter', 'test'),)", 'object_name': 'ImageChartTest'},
203+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
204+ 'image_chart_filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.ImageChartFilter']"}),
205+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
206+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Test']"})
207+ },
208+ 'dashboard_app.imagecharttestcase': {
209+ 'Meta': {'unique_together': "(('image_chart_filter', 'test_case'),)", 'object_name': 'ImageChartTestCase'},
210+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
211+ 'image_chart_filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.ImageChartFilter']"}),
212+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
213+ 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestCase']"})
214+ },
215+ 'dashboard_app.imagereport': {
216+ 'Meta': {'object_name': 'ImageReport'},
217+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
218+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
219+ 'is_published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
220+ 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'})
221+ },
222+ 'dashboard_app.imagereportchart': {
223+ 'Meta': {'object_name': 'ImageReportChart'},
224+ 'chart_type': ('django.db.models.fields.CharField', [], {'default': "'pass/fail'", 'max_length': '20'}),
225+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
226+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
227+ 'image_report': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['dashboard_app.ImageReport']"}),
228+ 'is_data_table_visible': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
229+ 'is_interactive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
230+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
231+ 'target_goal': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '10', 'decimal_places': '5', 'blank': 'True'})
232+ },
233+ 'dashboard_app.imageset': {
234+ 'Meta': {'object_name': 'ImageSet'},
235+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
236+ 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}),
237+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
238+ },
239+ 'dashboard_app.launchpadbug': {
240+ 'Meta': {'object_name': 'LaunchpadBug'},
241+ 'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}),
242+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243+ 'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"})
244+ },
245+ 'dashboard_app.namedattribute': {
246+ 'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'},
247+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
248+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249+ 'name': ('django.db.models.fields.TextField', [], {}),
250+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
251+ 'value': ('django.db.models.fields.TextField', [], {})
252+ },
253+ 'dashboard_app.pmqabundlestream': {
254+ 'Meta': {'object_name': 'PMQABundleStream'},
255+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.BundleStream']"}),
256+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
257+ },
258+ 'dashboard_app.softwarepackage': {
259+ 'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'},
260+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
261+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
262+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
263+ },
264+ 'dashboard_app.softwarepackagescratch': {
265+ 'Meta': {'object_name': 'SoftwarePackageScratch'},
266+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
267+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
268+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
269+ },
270+ 'dashboard_app.softwaresource': {
271+ 'Meta': {'object_name': 'SoftwareSource'},
272+ 'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
273+ 'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
274+ 'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
275+ 'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
276+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
277+ 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'})
278+ },
279+ 'dashboard_app.tag': {
280+ 'Meta': {'object_name': 'Tag'},
281+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
282+ 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'})
283+ },
284+ 'dashboard_app.test': {
285+ 'Meta': {'object_name': 'Test'},
286+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
287+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
288+ 'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
289+ },
290+ 'dashboard_app.testcase': {
291+ 'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'},
292+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
293+ 'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
294+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}),
295+ 'test_case_id': ('django.db.models.fields.TextField', [], {}),
296+ 'units': ('django.db.models.fields.TextField', [], {'blank': 'True'})
297+ },
298+ 'dashboard_app.testdefinition': {
299+ 'Meta': {'object_name': 'TestDefinition'},
300+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
301+ 'description': ('django.db.models.fields.TextField', [], {}),
302+ 'environment': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
303+ 'format': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
304+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
305+ 'location': ('django.db.models.fields.CharField', [], {'default': "'LOCAL'", 'max_length': '64'}),
306+ 'mime_type': ('django.db.models.fields.CharField', [], {'default': "'text/plain'", 'max_length': '64'}),
307+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
308+ 'target_dev_types': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
309+ 'target_os': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
310+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
311+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '256'})
312+ },
313+ 'dashboard_app.testresult': {
314+ 'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'},
315+ '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
316+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
317+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
318+ 'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
319+ 'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}),
320+ 'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
321+ 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
322+ 'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}),
323+ 'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
324+ 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}),
325+ 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}),
326+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
327+ },
328+ 'dashboard_app.testrun': {
329+ 'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'},
330+ 'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}),
331+ 'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
332+ 'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}),
333+ 'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}),
334+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
335+ 'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
336+ 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
337+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}),
338+ 'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}),
339+ 'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
340+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}),
341+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}),
342+ 'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
343+ },
344+ 'dashboard_app.testrundenormalization': {
345+ 'Meta': {'object_name': 'TestRunDenormalization'},
346+ 'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}),
347+ 'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}),
348+ 'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}),
349+ 'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}),
350+ 'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"})
351+ },
352+ 'dashboard_app.testrunfilter': {
353+ 'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'},
354+ 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
355+ 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
356+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
357+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}),
358+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
359+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
360+ 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['auth.User']"})
361+ },
362+ 'dashboard_app.testrunfilterattribute': {
363+ 'Meta': {'object_name': 'TestRunFilterAttribute'},
364+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}),
365+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
366+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
367+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
368+ },
369+ 'dashboard_app.testrunfiltersubscription': {
370+ 'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'},
371+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}),
372+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
373+ 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
374+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
375+ },
376+ 'dashboard_app.testrunfiltertest': {
377+ 'Meta': {'object_name': 'TestRunFilterTest'},
378+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tests'", 'to': "orm['dashboard_app.TestRunFilter']"}),
379+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
380+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
381+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.Test']"})
382+ },
383+ 'dashboard_app.testrunfiltertestcase': {
384+ 'Meta': {'object_name': 'TestRunFilterTestCase'},
385+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
386+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
387+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cases'", 'to': "orm['dashboard_app.TestRunFilterTest']"}),
388+ 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.TestCase']"})
389+ }
390+ }
391+
392+ complete_apps = ['dashboard_app']
393\ No newline at end of file
394
395=== modified file 'dashboard_app/models.py'
396--- dashboard_app/models.py 2013-09-05 16:00:38 +0000
397+++ dashboard_app/models.py 2013-09-13 13:13:49 +0000
398@@ -1540,101 +1540,6 @@
399 return self.name
400
401
402-class ImageReport(models.Model):
403-
404- name = models.SlugField(max_length=1024, unique=True)
405-
406- description = models.TextField(blank=True, null=True)
407-
408- def __unicode__(self):
409- return self.name
410-
411-
412-# Chart types
413-CHART_TYPES = ((r'pass/fail', 'Pass/Fail'),
414- (r'measurement', 'Measurement'))
415-# Chart representation
416-REPRESENTATION_TYPES = ((r'lines', 'Lines'),
417- (r'bars', 'Bars'))
418-
419-
420-class ImageReportChart(models.Model):
421-
422- name = models.CharField(max_length=100)
423-
424- image_report = models.ForeignKey(
425- ImageReport,
426- default=None,
427- null=False,
428- on_delete=models.CASCADE)
429-
430- test_runs = models.ManyToManyField(
431- TestRun,
432- through='ImageChartTestRun')
433-
434- test_cases = models.ManyToManyField(
435- TestCase,
436- through='ImageChartTestCase')
437-
438- chart_type = models.CharField(
439- max_length=20,
440- choices=CHART_TYPES,
441- verbose_name='Chart type')
442-
443- representation = models.CharField(
444- max_length=20,
445- choices=REPRESENTATION_TYPES,
446- verbose_name='Representation type')
447-
448- target_goal = models.DecimalField(
449- blank = True,
450- decimal_places = 5,
451- max_digits = 10,
452- null = True,
453- verbose_name = 'Target goal')
454-
455- is_interactive = models.BooleanField(
456- default=False,
457- verbose_name='Chart is interactive')
458-
459- is_data_table_visible = models.BooleanField(
460- default=False,
461- verbose_name='Data table is visible')
462-
463- def __unicode__(self):
464- return self.name
465-
466-
467-class ImageChartTestRun(models.Model):
468-
469- image_chart = models.ForeignKey(
470- ImageReportChart,
471- null=False,
472- on_delete=models.CASCADE)
473-
474- test_run = models.ForeignKey(
475- TestRun,
476- null=False,
477- on_delete=models.CASCADE)
478-
479- name = models.CharField(max_length=200)
480-
481-
482-class ImageChartTestCase(models.Model):
483-
484- image_chart = models.ForeignKey(
485- ImageReportChart,
486- null=False,
487- on_delete=models.CASCADE)
488-
489- test_case = models.ForeignKey(
490- TestCase,
491- null=False,
492- on_delete=models.CASCADE)
493-
494- name = models.CharField(max_length=200)
495-
496-
497 class LaunchpadBug(models.Model):
498
499 bug_id = models.PositiveIntegerField(unique=True)
500@@ -1923,3 +1828,137 @@
501 class PMQABundleStream(models.Model):
502
503 bundle_stream = models.ForeignKey(BundleStream, related_name='+')
504+
505+
506+class ImageReport(models.Model):
507+
508+ name = models.SlugField(max_length=1024, unique=True)
509+
510+ description = models.TextField(blank=True, null=True)
511+
512+ is_published = models.BooleanField(
513+ default=False,
514+ verbose_name='Published')
515+
516+ def __unicode__(self):
517+ return self.name
518+
519+ @models.permalink
520+ def get_absolute_url(self):
521+ return ("dashboard_app.views.image_reports.views.image_report_detail",
522+ (), dict(name=self.name))
523+
524+# Chart types
525+CHART_TYPES = ((r'pass/fail', 'Pass/Fail'),
526+ (r'measurement', 'Measurement'))
527+# Chart representation
528+REPRESENTATION_TYPES = ((r'lines', 'Lines'),
529+ (r'bars', 'Bars'))
530+
531+
532+class ImageReportChart(models.Model):
533+
534+ name = models.CharField(max_length=100)
535+
536+ description = models.TextField(blank=True, null=True)
537+
538+ image_report = models.ForeignKey(
539+ ImageReport,
540+ default=None,
541+ null=False,
542+ on_delete=models.CASCADE)
543+
544+ chart_type = models.CharField(
545+ max_length=20,
546+ choices=CHART_TYPES,
547+ verbose_name='Chart type',
548+ blank=False,
549+ default="pass/fail",
550+ )
551+
552+ target_goal = models.DecimalField(
553+ blank = True,
554+ decimal_places = 5,
555+ max_digits = 10,
556+ null = True,
557+ verbose_name = 'Target goal')
558+
559+ is_interactive = models.BooleanField(
560+ default=False,
561+ verbose_name='Chart is interactive')
562+
563+ is_data_table_visible = models.BooleanField(
564+ default=False,
565+ verbose_name='Data table is visible')
566+
567+ def __unicode__(self):
568+ return self.name
569+
570+ @models.permalink
571+ def get_absolute_url(self):
572+ return ("dashboard_app.views.image_reports.views.image_chart_detail",
573+ (), dict(id=self.id))
574+
575+
576+class ImageChartFilter(models.Model):
577+
578+ image_chart = models.ForeignKey(
579+ ImageReportChart,
580+ null=False,
581+ on_delete=models.CASCADE)
582+
583+ filter = models.ForeignKey(
584+ TestRunFilter,
585+ null=True,
586+ on_delete=models.SET_NULL)
587+
588+ representation = models.CharField(
589+ max_length=20,
590+ choices=REPRESENTATION_TYPES,
591+ verbose_name='Representation',
592+ blank=False,
593+ default="lines",
594+ )
595+
596+ @models.permalink
597+ def get_absolute_url(self):
598+ return (
599+ "dashboard_app.views.image_reports.views.image_chart_filter_edit",
600+ (), dict(id=self.id))
601+
602+
603+class ImageChartTest(models.Model):
604+
605+ class Meta:
606+ unique_together = ("image_chart_filter", "test")
607+
608+ image_chart_filter = models.ForeignKey(
609+ ImageChartFilter,
610+ null=False,
611+ on_delete=models.CASCADE)
612+
613+ test = models.ForeignKey(
614+ Test,
615+ null=False,
616+ on_delete=models.CASCADE)
617+
618+ name = models.CharField(max_length=200)
619+
620+
621+class ImageChartTestCase(models.Model):
622+
623+ class Meta:
624+ unique_together = ("image_chart_filter", "test_case")
625+
626+ image_chart_filter = models.ForeignKey(
627+ ImageChartFilter,
628+ null=False,
629+ on_delete=models.CASCADE)
630+
631+ test_case = models.ForeignKey(
632+ TestCase,
633+ null=False,
634+ on_delete=models.CASCADE)
635+
636+ name = models.CharField(max_length=200)
637+
638
639=== added file 'dashboard_app/static/dashboard_app/css/image-charts.css'
640--- dashboard_app/static/dashboard_app/css/image-charts.css 1970-01-01 00:00:00 +0000
641+++ dashboard_app/static/dashboard_app/css/image-charts.css 2013-09-13 13:13:49 +0000
642@@ -0,0 +1,82 @@
643+@import url("../../admin/css/widgets.css");
644+
645+div.selector { clear: both; }
646+div.selector span.helptext { display: none; }
647+div.selector h2 { margin: 0; font-size: 11pt; }
648+div.selector a { text-decoration: none; }
649+div.selector select { height: 10em; }
650+div.selector ul.selector-chooser { margin-top: 5.5em; }
651+div.selector .selector-chosen select {
652+ border: 1px solid rgb(204, 204, 204);
653+ border-top: none;
654+}
655+
656+.list-container {
657+ border: 1px solid #000000;
658+ clear: both;
659+ margin: 10px 10px 10px 10px;
660+ padding: 10px;
661+ width: 50%;
662+}
663+
664+.form-field {
665+ margin-bottom: 5px;
666+ vertical-align: top;
667+}
668+
669+.form-field label {
670+ vertical-align: top;
671+ width: 100px;
672+ display: inline-block;
673+ margin-left: 10px;
674+}
675+
676+.submit-button {
677+ margin-top: 20px;
678+ margin-left: 10px;
679+}
680+
681+.filter-headline {
682+ font-weight: bold;
683+ font-size: 16px;
684+}
685+
686+.filter-container {
687+ margin-bottom: 10px;
688+ clear: both;
689+}
690+
691+.filter-title {
692+ font-weight: bold;
693+ font-size: 15px;
694+ margin-bottom: 10px;
695+}
696+
697+.chart-title {
698+ font-weight: bold;
699+ font-size: 15px;
700+ margin-bottom: 10px;
701+}
702+
703+.errors {
704+ color: red;
705+}
706+
707+.fields-container {
708+ margin-left: 10px;
709+}
710+
711+#filters_div {
712+ margin: 10px 0 0 10px;
713+ border: 1px solid #000000;
714+ clear: both;
715+ width: 75%;
716+ padding: 5px 0 10px 10px;
717+ overflow: auto;
718+}
719+
720+#alias_container {
721+ font-weight: bold;
722+ float: left;
723+ display: none;
724+}
725\ No newline at end of file
726
727=== added file 'dashboard_app/static/dashboard_app/images/ajax-progress.gif'
728Binary 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-13 13:13:49 +0000 differ
729=== added file 'dashboard_app/static/dashboard_app/js/image-report-editor.js'
730--- dashboard_app/static/dashboard_app/js/image-report-editor.js 1970-01-01 00:00:00 +0000
731+++ dashboard_app/static/dashboard_app/js/image-report-editor.js 2013-09-13 13:13:49 +0000
732@@ -0,0 +1,221 @@
733+select_filter = function() {
734+ // Open the filter select dialog.
735+ $('#filter_select_dialog').dialog('open');
736+}
737+
738+filters_callback = function(id, name) {
739+ // Function which will be called when a filter is selected from the dialog.
740+
741+ if ($('#id_chart_type').val() == "pass/fail") {
742+ url = "/dashboard/filters/+get-tests-json";
743+ } else {
744+ url = "/dashboard/filters/+get-test-cases-json";
745+ }
746+
747+ $.ajax({
748+ url: url,
749+ async: false,
750+ data: {"id": id},
751+ beforeSend: function () {
752+ $('#filter-container').remove();
753+ $('#filter_select_dialog').dialog('close');
754+ $('#loading_dialog').dialog('open');
755+ },
756+ success: function (data) {
757+ $('#loading_dialog').dialog('close');
758+ $("#id_filter").val(id);
759+ add_filter_container(data, name);
760+ },
761+ error: function(data, status, error) {
762+ $('#loading_dialog').dialog('close');
763+ alert('Filter could not be loaded, please try again.');
764+ }
765+ });
766+}
767+
768+add_filter_container = function(data, title) {
769+ // Adds elements which contain tests or test cases from the previously
770+ // selected filter.
771+
772+ content = '<hr><div class="filter-title">' + title + '</div>';
773+
774+ if ($('#id_chart_type').val() == "pass/fail") {
775+ test_label = "Tests";
776+ } else {
777+ test_label = "Test Cases";
778+ }
779+
780+ content += '<div class="selector"><div class="selector-available"><h2>' +
781+ 'Select ' + test_label + '</h2>';
782+
783+ content += '<select id="available_tests" multiple class="filtered">';
784+ for (i in data) {
785+ if ($('#id_chart_type').val() == "pass/fail") {
786+ content += '<option value="' + data[i].pk + '">' +
787+ data[i].fields.test_id + '</option>';
788+ } else {
789+ content += '<option value="' + data[i].pk + '">' +
790+ data[i].fields.test_case_id + '</option>';
791+ }
792+ }
793+ content += '</select>';
794+
795+ content += '<a id="add_all_link" href="javascript: void(0)">' +
796+ 'Choose All</a>';
797+ content += '</div>';
798+
799+ content += '<ul class="selector-chooser">' +
800+ '<li><a href="javascript: void(0)" id="add_link"' +
801+ 'class="selector-add active"></a></li>' +
802+ '<li><a href="javascript: void(0)" id="remove_link"' +
803+ 'class="selector-remove active"></a></li>' +
804+ '</ul>';
805+
806+ content += '<div class="selector-chosen"><h2>' +
807+ 'Choosen ' + test_label + '</h2>';
808+
809+ content += '<select id="chosen_tests" onchange="toggle_alias()" multiple class="filtered"></select>';
810+ content += '<a id="remove_all_link" href="javascript: void(0)">' +
811+ 'Remove All</a>';
812+ content += '</div></div>';
813+
814+ content += '<div id="alias_container">Alias<br/>';
815+ content += '<input type="text" onkeyup="copy_alias(this);" id="alias" />';
816+ content += '</div>';
817+
818+ $('<div id="filter-container"></div>').html(
819+ content).appendTo($('#filters_div'));
820+
821+ update_events();
822+}
823+
824+update_events = function() {
825+ // Add onclick events to the links controlling the select boxes.
826+
827+ $('#add_link').click(function() {
828+ move_options('available_tests', 'chosen_tests');
829+ });
830+ $('#remove_link').click(function() {
831+ move_options('chosen_tests', 'available_tests');
832+ });
833+ $('#add_all_link').click(function() {
834+ $('#available_tests option').each(function() {
835+ $(this).attr('selected', 'selected');
836+ });
837+ move_options('available_tests', 'chosen_tests');
838+ });
839+ $('#remove_all_link').click(function() {
840+ $('#chosen_tests option').each(function() {
841+ $(this).attr('selected', 'selected');
842+ });
843+ move_options('chosen_tests', 'available_tests');
844+ });
845+}
846+
847+move_options = function(from_element, to_element) {
848+ var options = $("#" + from_element + " option:selected");
849+ $("#" + to_element).append(options.clone());
850+ $(options).remove();
851+
852+ update_aliases();
853+ toggle_alias();
854+}
855+
856+add_selected_options = function() {
857+ // Adds options from chosen tests select box as hidden fields.
858+
859+ $('#chosen_tests option').each(function() {
860+ if ($('#id_chart_type').val() == "pass/fail") {
861+ field_name = "image_chart_tests";
862+ } else {
863+ field_name = "image_chart_test_cases";
864+ }
865+ $('<input type="hidden" name="' + field_name +
866+ '" value="'+ $(this).val() + '" />').appendTo($('#add_filter_link'));
867+ });
868+}
869+
870+update_aliases = function() {
871+ // Update hidden aliases inputs based on chosen tests.
872+
873+ $('#chosen_tests option').each(function() {
874+ if ($('#alias_' + $(this).val()).length == 0) {
875+ $('<input type="hidden" class="alias" data-sid="' + $(this).val() +
876+ '" name="aliases" id="alias_' + $(this).val() +
877+ '" />').appendTo($('#aliases_div'));
878+ }
879+ });
880+ chosen_tests = $.map($('#chosen_tests option'), function(e) {
881+ return e.value;
882+ });
883+ $('.alias').each(function(index, value) {
884+ test_id = value.id.split('_')[1];
885+
886+ if (chosen_tests.indexOf(test_id) == -1) {
887+ $('#alias_' + test_id).remove();
888+ }
889+ });
890+}
891+
892+toggle_alias = function() {
893+ // Show/hide alias input field.
894+
895+ if ($('#chosen_tests option:selected').length == 1) {
896+ $('#alias_container').show();
897+ test_id = $('#chosen_tests option:selected').val();
898+ $('#alias').val($('#alias_' + test_id).val());
899+ } else {
900+ $('#alias_container').hide();
901+ }
902+}
903+
904+copy_alias = function(e) {
905+ // Populate alias input based on the selected test.
906+
907+ if ($('#chosen_tests option:selected').length == 1) {
908+ test_id = $('#chosen_tests option:selected').val();
909+ $('#alias_' + test_id).val(e.value);
910+ }
911+}
912+
913+sort_aliases = function() {
914+ // Pre submit function. Sort the aliases hidden inputs.
915+
916+ $('#aliases_div input').sort(function(a,b) {
917+ return a.dataset.sid > b.dataset.sid;
918+ }).appendTo('#aliases_div');
919+}
920+
921+init_filter_dialog = function() {
922+ // Setup the filter table dialog.
923+
924+ var filter_dialog = $('<div id="filter_select_dialog"></div>');
925+ $('#all-filters_wrapper').wrapAll(filter_dialog);
926+
927+ $('#filter_select_dialog').dialog({
928+ autoOpen: false,
929+ title: 'Select Filter',
930+ draggable: false,
931+ height: 280,
932+ width: 420,
933+ modal: true,
934+ resizable: false
935+ });
936+}
937+
938+init_loading_dialog = function() {
939+ // Setup the loading image dialog.
940+
941+ $('#loading_dialog').dialog({
942+ autoOpen: false,
943+ title: '',
944+ draggable: false,
945+ height: 35,
946+ width: 250,
947+ modal: true,
948+ resizable: false,
949+ dialogClass: 'loading-dialog'
950+ });
951+
952+ $('.loading-dialog div.ui-dialog-titlebar').hide();
953+}
954
955=== added file 'dashboard_app/templates/dashboard_app/image_chart_filter_form.html'
956--- dashboard_app/templates/dashboard_app/image_chart_filter_form.html 1970-01-01 00:00:00 +0000
957+++ dashboard_app/templates/dashboard_app/image_chart_filter_form.html 2013-09-13 13:13:49 +0000
958@@ -0,0 +1,109 @@
959+{% extends "dashboard_app/_content.html" %}
960+{% load i18n %}
961+{% load django_tables2 %}
962+
963+{% block extrahead %}
964+{{ block.super }}
965+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
966+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/image-report-editor.js"></script>
967+
968+{% endblock %}
969+
970+
971+{% block content %}
972+<h1>Image Chart Filter</h1>
973+
974+{% block content_form %}
975+<form action="" method="post">{% csrf_token %}
976+
977+ {% if form.errors %}
978+ <div class="errors">
979+ <div>
980+ {{ form.non_field_errors }}
981+ <ul>
982+ {% for field in form %}
983+ {% if field.errors %}
984+ <li>{{ field.label }}: {{ field.errors|striptags }}</li>
985+ {% endif %}
986+ {% endfor %}
987+ </ul>
988+ </div>
989+ </div>
990+ {% endif %}
991+
992+ <div id="filters_div">
993+ <div id="add_filter_link">
994+ <a href="#" onclick="select_filter()">Select filter</a>
995+ {{ form.filter }}
996+ {{ form.image_chart }}
997+ <input type="hidden" id="id_chart_type" value="{{ image_chart.chart_type }}"/>
998+ {{ form.image_chart_tests }}
999+ {{ form.image_chart_test_cases }}
1000+ </div>
1001+
1002+ <div>
1003+ {{ form.representation.label_tag }}
1004+ {{ form.representation }}
1005+ </div>
1006+ </div>
1007+
1008+ <div id="aliases_div">
1009+ </div>
1010+
1011+ <div class="submit-button">
1012+ <input type="submit" value="Save" />
1013+ </div>
1014+</form>
1015+
1016+{% endblock content_form %}
1017+
1018+{% render_table filters_table %}
1019+
1020+<div id="loading_dialog">
1021+<img src="{{ STATIC_URL }}dashboard_app/images/ajax-progress.gif" alt="Loading..." />
1022+</div>
1023+
1024+<script type="text/javascript">
1025+ $().ready(function () {
1026+
1027+ init_filter_dialog();
1028+ init_loading_dialog();
1029+
1030+ $('form').submit(function() {
1031+ add_selected_options();
1032+ sort_aliases();
1033+ });
1034+
1035+ {% if form.filter.value %}
1036+ filters_callback('{{ instance.filter.id }}',
1037+ '{{ instance.filter.name }}');
1038+
1039+ if ($('#id_chart_type').val() == "pass/fail") {
1040+ {% for test in instance.imagecharttest_set.all %}
1041+ $('#available_tests option[value="{{ test.test_id }}"]').attr('selected', 'selected');
1042+ {% endfor %}
1043+ } else {
1044+ {% for test in instance.imagecharttestcase_set.all %}
1045+ $('#available_tests option[value="{{ test.test_case_id }}"]').attr('selected', 'selected');
1046+ {% endfor %}
1047+ }
1048+
1049+ move_options('available_tests', 'chosen_tests');
1050+
1051+ if ($('#id_chart_type').val() == "pass/fail") {
1052+ {% for test in instance.imagecharttest_set.all %}
1053+ $('#alias_{{ test.test_id }}').val('{{ test.name }}');
1054+ {% endfor %}
1055+ } else {
1056+ {% for test in instance.imagecharttestcase_set.all %}
1057+
1058+ $('#alias_{{ test.test_case_id }}').val('{{ test.name }}');
1059+ {% endfor %}
1060+ }
1061+
1062+ {% endif %}
1063+});
1064+
1065+</script>
1066+
1067+{% endblock %}
1068
1069=== added file 'dashboard_app/templates/dashboard_app/image_report_chart_detail.html'
1070--- dashboard_app/templates/dashboard_app/image_report_chart_detail.html 1970-01-01 00:00:00 +0000
1071+++ dashboard_app/templates/dashboard_app/image_report_chart_detail.html 2013-09-13 13:13:49 +0000
1072@@ -0,0 +1,86 @@
1073+{% extends "dashboard_app/_content.html" %}
1074+{% load i18n %}
1075+
1076+{% block extrahead %}
1077+{{ block.super }}
1078+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
1079+{% endblock %}
1080+
1081+{% block content %}
1082+
1083+<h1>Image Chart {{ image_chart.name }}</h1>
1084+
1085+
1086+<div class="fields-container">
1087+ <div class="form-field">
1088+ <a href="{{ image_chart.get_absolute_url }}/+edit">Edit</a> this chart.
1089+ </div>
1090+ <div class="form-field">
1091+ Description: {{ image_chart.description }}
1092+ </div>
1093+ <div class="form-field">
1094+ Chart type: {{ image_chart.chart_type }}
1095+ </div>
1096+ <div class="form-field">
1097+ Data table visible: {{ image_chart.is_data_table_visible }}
1098+ </div>
1099+ <div class="form-field">
1100+ Target goal: {{ image_chart.target_goal|floatformat:"-2" }}
1101+ </div>
1102+</div>
1103+
1104+
1105+<h3>Filters</h3>
1106+
1107+<div class="fields-container">
1108+ <div id="add_filter_link">
1109+ <a href="{{ image_chart.get_absolute_url }}/+add-filter">Add filter</a>
1110+ </div>
1111+</div>
1112+
1113+<div class="list-container">
1114+ {% for chart_filter in image_chart.imagechartfilter_set.all %}
1115+ <div class="chart-title">
1116+ {{ chart_filter.filter.name }}&nbsp;&nbsp;&nbsp;&nbsp;
1117+ <a style="font-size: 13px;" href="{{ chart_filter.get_absolute_url }}">
1118+ edit
1119+ </a>&nbsp;
1120+ <a style="font-size: 13px;" href="{{ chart_filter.get_absolute_url }}/+delete">
1121+ remove
1122+ </a>
1123+ </div>
1124+ <div>
1125+ {% if image_chart.chart_type == "pass/fail" %}
1126+ Tests:&nbsp;
1127+ {% for chart_test in chart_filter.imagecharttest_set.all %}
1128+ {% if forloop.last %}
1129+ {{ chart_test.test.test_id }}
1130+ {% else %}
1131+ {{ chart_test.test.test_id }},&nbsp;
1132+ {% endif %}
1133+ {% endfor %}
1134+ {% else %}
1135+ Test Cases:&nbsp
1136+ {% for chart_test in chart_filter.imagecharttestcase_set.all %}
1137+ {% if forloop.last %}
1138+ {{ chart_test.test_case.test_case_id }}
1139+ {% else %}
1140+ {{ chart_test.test_case.test_case_id }},&nbsp;
1141+ {% endif %}
1142+ {% endfor %}
1143+ {% endif %}
1144+ </div>
1145+ <div>
1146+ Representation: {{ chart_filter.representation }}
1147+ </div>
1148+
1149+ <hr/>
1150+ {% empty %}
1151+ <div>
1152+ <li>No filters added yet.</li>
1153+ </div>
1154+ {% endfor %}
1155+</div>
1156+
1157+
1158+{% endblock %}
1159
1160=== added file 'dashboard_app/templates/dashboard_app/image_report_chart_form.html'
1161--- dashboard_app/templates/dashboard_app/image_report_chart_form.html 1970-01-01 00:00:00 +0000
1162+++ dashboard_app/templates/dashboard_app/image_report_chart_form.html 2013-09-13 13:13:49 +0000
1163@@ -0,0 +1,62 @@
1164+{% extends "dashboard_app/_content.html" %}
1165+{% load i18n %}
1166+{% load django_tables2 %}
1167+
1168+{% block extrahead %}
1169+{{ block.super }}
1170+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
1171+
1172+{% endblock %}
1173+
1174+
1175+{% block content %}
1176+<h1>Add Image Charts 2.0</h1>
1177+
1178+{% block content_form %}
1179+<form action="" method="post">{% csrf_token %}
1180+
1181+ {% if form.errors %}
1182+ <div class="errors">
1183+ <div>
1184+ {{ form.non_field_errors }}
1185+ <ul>
1186+ {% for field in form %}
1187+ {% if field.errors %}
1188+ <li>{{ field.label }}: {{ field.errors|striptags }}</li>
1189+ {% endif %}
1190+ {% endfor %}
1191+ </ul>
1192+ </div>
1193+ </div>
1194+ {% endif %}
1195+
1196+ <div class="form-field">
1197+ {{ form.name.label_tag }}
1198+ {{ form.name }}
1199+ <input type="hidden" id="id_image_report" name="image_report" value="{{ image_report_id }}"/>
1200+ </div>
1201+ <div class="form-field">
1202+ {{ form.description.label_tag }}
1203+ {{ form.description }}
1204+ </div>
1205+ <div class="form-field">
1206+ {{ form.chart_type.label_tag }}
1207+ {{ form.chart_type }}
1208+ </div>
1209+ <div class="form-field">
1210+ {{ form.is_data_table_visible.label_tag }}
1211+ {{ form.is_data_table_visible }}
1212+ </div>
1213+ <div class="form-field">
1214+ {{ form.target_goal.label_tag }}
1215+ {{ form.target_goal }}
1216+ </div>
1217+
1218+ <div class="submit-button">
1219+ <input type="submit" value="Save" />
1220+ </div>
1221+</form>
1222+
1223+{% endblock content_form %}
1224+
1225+{% endblock %}
1226
1227=== added file 'dashboard_app/templates/dashboard_app/image_report_detail.html'
1228--- dashboard_app/templates/dashboard_app/image_report_detail.html 1970-01-01 00:00:00 +0000
1229+++ dashboard_app/templates/dashboard_app/image_report_detail.html 2013-09-13 13:13:49 +0000
1230@@ -0,0 +1,80 @@
1231+{% extends "dashboard_app/_content.html" %}
1232+{% load i18n %}
1233+
1234+{% block extrahead %}
1235+{{ block.super }}
1236+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
1237+{% endblock %}
1238+
1239+{% block content %}
1240+
1241+<h1>Image Report {{ image_report.name }}</h1>
1242+
1243+<div class="fields-container">
1244+ <div class="form-field">
1245+ Status&#58;&nbsp;
1246+ {% if image_report.is_published %}
1247+ <span style="font-weight: bold; color: green;">
1248+ Published
1249+ </span>
1250+ {% else %}
1251+ <span style="font-weight: bold; color: orange;">
1252+ Not Published
1253+ </span>
1254+ {% endif %}
1255+ </div>
1256+ <div class="form-field">
1257+ Description&#58;&nbsp;{{ image_report.description }}
1258+ </div>
1259+ <div class="form-field">
1260+ <a href="{{ image_report.get_absolute_url }}/+edit">Edit</a> this image report.
1261+ </div>
1262+ <div class="form-field">
1263+ {% if image_report.is_published %}
1264+ <a href="{{ image_report.get_absolute_url }}/+unpublish">Unpublish</a> this image report.
1265+ {% else %}
1266+ <a href="{{ image_report.get_absolute_url }}/+publish">Publish</a> this image report.
1267+{% endif %}
1268+ </div>
1269+</div>
1270+
1271+<h3>Charts</h3>
1272+
1273+<div class="fields-container">
1274+ <a href="{% url dashboard_app.views.image_reports.views.image_chart_add %}?image_report_id={{ image_report.id }}">
1275+ Add new chart
1276+ </a>
1277+</div>
1278+
1279+<div class="list-container">
1280+ {% for image_chart in image_report.imagereportchart_set.all %}
1281+ <div class="chart-title">
1282+ {{ image_chart.name }}&nbsp;&nbsp;
1283+ <a style="font-size: 13px;" href="{{ image_chart.get_absolute_url }}">
1284+ details
1285+ </a>&nbsp;
1286+ <a style="font-size: 13px;" href="{{ image_chart.get_absolute_url }}">
1287+ preview
1288+ </a>
1289+ </div>
1290+ <div>
1291+ Description: {{ image_chart.description }}
1292+ </div>
1293+ <div>
1294+ Chart type: {{ image_chart.chart_type }}
1295+ </div>
1296+ <div>
1297+ Data table visible: {{ image_chart.is_data_table_visible }}
1298+ </div>
1299+ <div>
1300+ Target goal: {{ image_chart.target_goal|floatformat:"-2" }}
1301+ </div>
1302+ <hr/>
1303+ {% empty %}
1304+ <div>
1305+ <li>No charts added yet.</li>
1306+ </div>
1307+ {% endfor %}
1308+</div>
1309+
1310+{% endblock %}
1311
1312=== added file 'dashboard_app/templates/dashboard_app/image_report_form.html'
1313--- dashboard_app/templates/dashboard_app/image_report_form.html 1970-01-01 00:00:00 +0000
1314+++ dashboard_app/templates/dashboard_app/image_report_form.html 2013-09-13 13:13:49 +0000
1315@@ -0,0 +1,45 @@
1316+{% extends "dashboard_app/_content.html" %}
1317+
1318+{% block extrahead %}
1319+{{ block.super }}
1320+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
1321+{% endblock %}
1322+
1323+{% block content %}
1324+<h1>Image Reports 2.0</h1>
1325+
1326+{% block content_form %}
1327+<form action="" method="post">{% csrf_token %}
1328+
1329+ {% if form.errors %}
1330+ <div class="errors">
1331+ <div>
1332+ {{ form.non_field_errors }}
1333+ <ul>
1334+ {% for field in form %}
1335+ {% if field.errors %}
1336+ <li>{{ field.label }}: {{ field.errors|striptags }}</li>
1337+ {% endif %}
1338+ {% endfor %}
1339+ </ul>
1340+ </div>
1341+ </div>
1342+ {% endif %}
1343+
1344+<div class="form-field">
1345+ {{ form.name.label_tag }}
1346+ {{ form.name }}
1347+</div>
1348+<div class="form-field">
1349+ {{ form.description.label_tag }}
1350+ {{ form.description }}
1351+</div>
1352+
1353+<div class="submit-button">
1354+<input type="submit" value="Save" />
1355+</div>
1356+</form>
1357+
1358+{% endblock content_form %}
1359+
1360+{% endblock %}
1361
1362=== added file 'dashboard_app/templates/dashboard_app/image_report_list.html'
1363--- dashboard_app/templates/dashboard_app/image_report_list.html 1970-01-01 00:00:00 +0000
1364+++ dashboard_app/templates/dashboard_app/image_report_list.html 2013-09-13 13:13:49 +0000
1365@@ -0,0 +1,37 @@
1366+{% extends "dashboard_app/_content.html" %}
1367+
1368+{% block extrahead %}
1369+{{ block.super }}
1370+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-charts.css"/>
1371+{% endblock %}
1372+
1373+{% block content %}
1374+<h1>Image Reports 2.0</h1>
1375+
1376+<p style="margin-left: 10px;">
1377+ <a href="{% url dashboard_app.views.image_reports.views.image_report_add %}">
1378+ Add new Image Report
1379+ </a>
1380+</p>
1381+
1382+{% for image_report in image_reports %}
1383+<div class="list-container">
1384+ <div style="float: left;">
1385+ <a href="{{ image_report.get_absolute_url }}">{{ image_report.name }}</a>
1386+ &nbsp;&nbsp;
1387+ </div>
1388+ {% if image_report.is_published %}
1389+ <div style="font-weight: bold; float: right; color: green;">
1390+ Published
1391+ </div>
1392+ {% else %}
1393+ <div style="font-weight: bold; float: right; color: orange;">
1394+ Not Published
1395+ </div>
1396+ {% endif %}
1397+ <div style="clear: both;">
1398+ {{ image_report.description }}
1399+ </div>
1400+</div>
1401+{% endfor %}
1402+{% endblock %}
1403
1404=== modified file 'dashboard_app/urls.py'
1405--- dashboard_app/urls.py 2013-09-05 11:27:15 +0000
1406+++ dashboard_app/urls.py 2013-09-13 13:13:49 +0000
1407@@ -33,6 +33,8 @@
1408 url(r'^filters/\+add$', 'filters.views.filter_add'),
1409 url(r'^filters/\+add-preview-json$', 'filters.views.filter_preview_json'),
1410 url(r'^filters/\+add-cases-for-test-json$', 'filters.views.filter_add_cases_for_test_json'),
1411+ url(r'^filters/\+get-tests-json$', 'filters.views.get_tests_json'),
1412+ url(r'^filters/\+get-test-cases-json$', 'filters.views.get_test_cases_json'),
1413 url(r'^filters/\+attribute-name-completion-json$', 'filters.views.filter_attr_name_completion_json'),
1414 url(r'^filters/\+attribute-value-completion-json$', 'filters.views.filter_attr_value_completion_json'),
1415 url(r'^filters/~(?P<username>[^/]+)/(?P<name>[a-zA-Z0-9-_]+)$', 'filters.views.filter_detail'),
1416@@ -63,6 +65,18 @@
1417 url(r'^permalink/bundle/(?P<content_sha1>[0-9a-z]+)/$', 'redirect_to_bundle'),
1418 url(r'^permalink/bundle/(?P<content_sha1>[0-9a-z]+)/(?P<trailing>.*)$', 'redirect_to_bundle'),
1419 url(r'^image-reports/$', 'images.image_report_list'),
1420+ url(r'^image-charts/$', 'image_reports.views.image_report_list'),
1421+ url(r'^image-charts/(?P<name>[a-zA-Z0-9-_]+)$', 'image_reports.views.image_report_detail'),
1422+ url(r'^image-charts/\+add$', 'image_reports.views.image_report_add'),
1423+ url(r'^image-charts/(?P<name>[a-zA-Z0-9-_]+)/\+edit$', 'image_reports.views.image_report_edit'),
1424+ url(r'^image-charts/(?P<name>[a-zA-Z0-9-_]+)/\+publish$', 'image_reports.views.image_report_publish'),
1425+ url(r'^image-charts/(?P<name>[a-zA-Z0-9-_]+)/\+unpublish$', 'image_reports.views.image_report_unpublish'),
1426+ url(r'^image-chart/(?P<id>[a-zA-Z0-9-_]+)$', 'image_reports.views.image_chart_detail'),
1427+ url(r'^image-chart/\+add$', 'image_reports.views.image_chart_add'),
1428+ url(r'^image-chart/(?P<id>[a-zA-Z0-9-_]+)/\+edit$', 'image_reports.views.image_chart_edit'),
1429+ url(r'^image-chart/(?P<id>[a-zA-Z0-9-_]+)/\+add-filter$', 'image_reports.views.image_chart_filter_add'),
1430+ url(r'^image-chart-filter/(?P<id>[a-zA-Z0-9-_]+)$', 'image_reports.views.image_chart_filter_edit'),
1431+ url(r'^image-chart-filter/(?P<id>[a-zA-Z0-9-_]+)/\+delete$', 'image_reports.views.image_chart_filter_delete'),
1432 url(r'^pmqa$', 'pmqa.pmqa_view'),
1433 url(r'^pmqa(?P<pathname>/[a-zA-Z0-9/._-]+/)(?P<device_type>[a-zA-Z0-9-_]+)$', 'pmqa.pmqa_filter_view'),
1434 url(r'^pmqa(?P<pathname>/[a-zA-Z0-9/._-]+/)(?P<device_type>[a-zA-Z0-9-_]+)/json$', 'pmqa.pmqa_filter_view_json'),
1435
1436=== modified file 'dashboard_app/views/filters/tables.py'
1437--- dashboard_app/views/filters/tables.py 2013-01-10 01:56:51 +0000
1438+++ dashboard_app/views/filters/tables.py 2013-09-13 13:13:49 +0000
1439@@ -109,6 +109,15 @@
1440 return TestRunFilter.objects.filter(public=True)
1441
1442
1443+class AllFiltersSimpleTable(DataTablesTable):
1444+
1445+ name = TemplateColumn('''
1446+ <a href="#" onclick="filters_callback('{{ record.id }}', '{{ record.name }}');">{{ record.name }}</a>
1447+ ''')
1448+
1449+ def get_queryset(self):
1450+ return TestRunFilter.objects.all()
1451+
1452
1453 class TestRunColumn(Column):
1454 def render(self, record):
1455
1456=== modified file 'dashboard_app/views/filters/views.py'
1457--- dashboard_app/views/filters/views.py 2013-01-10 01:56:51 +0000
1458+++ dashboard_app/views/filters/views.py 2013-09-13 13:13:49 +0000
1459@@ -20,6 +20,7 @@
1460
1461 from django.contrib.auth.decorators import login_required
1462 from django.contrib.contenttypes.models import ContentType
1463+from django.core import serializers
1464 from django.core.exceptions import PermissionDenied, ValidationError
1465 from django.core.urlresolvers import reverse
1466 from django.http import HttpResponse, HttpResponseRedirect
1467@@ -37,6 +38,7 @@
1468 evaluate_filter,
1469 )
1470 from dashboard_app.models import (
1471+ Bundle,
1472 NamedAttribute,
1473 Test,
1474 TestCase,
1475@@ -234,6 +236,24 @@
1476 mimetype='application/json')
1477
1478
1479+def get_tests_json(request):
1480+
1481+ tests = Test.objects.filter(
1482+ test_runs__bundle__bundle_stream__testrunfilter__id=request.GET['id']).distinct()
1483+
1484+ data = serializers.serialize('json', tests)
1485+ return HttpResponse(data, mimetype='application/json')
1486+
1487+
1488+def get_test_cases_json(request):
1489+
1490+ test_cases = TestCase.objects.filter(
1491+ test__test_runs__bundle__bundle_stream__testrunfilter__id=request.GET['id']).distinct()
1492+
1493+ data = serializers.serialize('json', test_cases)
1494+ return HttpResponse(data, mimetype='application/json')
1495+
1496+
1497 def filter_attr_name_completion_json(request):
1498 term = request.GET['term']
1499 content_type_id = ContentType.objects.get_for_model(TestRun).id
1500
1501=== added directory 'dashboard_app/views/image_reports'
1502=== added file 'dashboard_app/views/image_reports/__init__.py'
1503--- dashboard_app/views/image_reports/__init__.py 1970-01-01 00:00:00 +0000
1504+++ dashboard_app/views/image_reports/__init__.py 2013-09-13 13:13:49 +0000
1505@@ -0,0 +1,17 @@
1506+# Copyright (C) 2010-2013 Linaro Limited
1507+#
1508+# Author: Stevan Radakovic <stevan.radakovic@linaro.org>
1509+#
1510+# This file is part of Launch Control.
1511+#
1512+# Launch Control is free software: you can redistribute it and/or modify
1513+# it under the terms of the GNU Affero General Public License version 3
1514+# as published by the Free Software Foundation
1515+#
1516+# Launch Control is distributed in the hope that it will be useful,
1517+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1518+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1519+# GNU General Public License for more details.
1520+#
1521+# You should have received a copy of the GNU Affero General Public License
1522+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
1523
1524=== added file 'dashboard_app/views/image_reports/forms.py'
1525--- dashboard_app/views/image_reports/forms.py 1970-01-01 00:00:00 +0000
1526+++ dashboard_app/views/image_reports/forms.py 2013-09-13 13:13:49 +0000
1527@@ -0,0 +1,91 @@
1528+# Copyright (C) 2010-2013 Linaro Limited
1529+#
1530+# Author: Stevan Radakovic <stevan.radakovic@linaro.org>
1531+#
1532+# This file is part of Launch Control.
1533+#
1534+# Launch Control is free software: you can redistribute it and/or modify
1535+# it under the terms of the GNU Affero General Public License version 3
1536+# as published by the Free Software Foundation
1537+#
1538+# Launch Control is distributed in the hope that it will be useful,
1539+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1540+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1541+# GNU General Public License for more details.
1542+#
1543+# You should have received a copy of the GNU Affero General Public License
1544+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
1545+
1546+from django import forms
1547+
1548+from dashboard_app.models import (
1549+ ImageReport,
1550+ ImageReportChart,
1551+ ImageChartFilter,
1552+ ImageChartTest,
1553+ ImageChartTestCase,
1554+ Test,
1555+ TestCase,
1556+)
1557+
1558+
1559+class ImageReportEditorForm(forms.ModelForm):
1560+ class Meta:
1561+ model = ImageReport
1562+ exclude = ('owner', 'is_published',)
1563+
1564+ def save(self, commit=True, **kwargs):
1565+ instance = super(ImageReportEditorForm,
1566+ self).save(commit=commit, **kwargs)
1567+ return instance
1568+
1569+ def is_valid(self):
1570+ return super(ImageReportEditorForm, self).is_valid()
1571+
1572+ def full_clean(self):
1573+ super(ImageReportEditorForm, self).full_clean()
1574+
1575+ def __init__(self, user, *args, **kwargs):
1576+ super(ImageReportEditorForm, self).__init__(*args, **kwargs)
1577+
1578+
1579+class ImageReportChartForm(forms.ModelForm):
1580+ class Meta:
1581+ model = ImageReportChart
1582+ widgets = {'image_report': forms.HiddenInput}
1583+
1584+ def __init__(self, user, *args, **kwargs):
1585+ super(ImageReportChartForm, self).__init__(*args, **kwargs)
1586+ if len(self.instance.imagechartfilter_set.all()) != 0:
1587+ self.fields['chart_type'].label = ""
1588+ self.fields['chart_type'].widget = forms.HiddenInput()
1589+
1590+ def save(self, commit=True, **kwargs):
1591+ instance = super(ImageReportChartForm,
1592+ self).save(commit=commit, **kwargs)
1593+ return instance
1594+
1595+
1596+class ImageChartFilterForm(forms.ModelForm):
1597+
1598+ image_chart_tests = forms.ModelMultipleChoiceField(
1599+ widget=forms.MultipleHiddenInput,
1600+ queryset=Test.objects.all().order_by("id"),
1601+ required=False)
1602+ image_chart_test_cases = forms.ModelMultipleChoiceField(
1603+ widget=forms.MultipleHiddenInput,
1604+ queryset=TestCase.objects.all().order_by("id"),
1605+ required=False)
1606+
1607+ class Meta:
1608+ model = ImageChartFilter
1609+ widgets = {'filter': forms.HiddenInput,
1610+ 'image_chart': forms.HiddenInput,}
1611+
1612+ def __init__(self, user, *args, **kwargs):
1613+ super(ImageChartFilterForm, self).__init__(*args, **kwargs)
1614+
1615+ def save(self, commit=True, **kwargs):
1616+ instance = super(ImageChartFilterForm,
1617+ self).save(commit=commit, **kwargs)
1618+ return instance
1619
1620=== added file 'dashboard_app/views/image_reports/views.py'
1621--- dashboard_app/views/image_reports/views.py 1970-01-01 00:00:00 +0000
1622+++ dashboard_app/views/image_reports/views.py 2013-09-13 13:13:49 +0000
1623@@ -0,0 +1,325 @@
1624+# Copyright (C) 2010-2013 Linaro Limited
1625+#
1626+# Author: Stevan Radakovic <stevan.radakovic@linaro.org>
1627+#
1628+# This file is part of Launch Control.
1629+#
1630+# Launch Control is free software: you can redistribute it and/or modify
1631+# it under the terms of the GNU Affero General Public License version 3
1632+# as published by the Free Software Foundation
1633+#
1634+# Launch Control is distributed in the hope that it will be useful,
1635+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1636+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1637+# GNU General Public License for more details.
1638+#
1639+# You should have received a copy of the GNU Affero General Public License
1640+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
1641+
1642+import json
1643+
1644+from django.contrib.auth.decorators import login_required
1645+from django.core.exceptions import PermissionDenied, ValidationError
1646+from django.http import HttpResponse, HttpResponseRedirect
1647+from django.shortcuts import render_to_response
1648+from django.template import RequestContext
1649+from django.utils.safestring import mark_safe
1650+
1651+from lava_server.bread_crumbs import (
1652+ BreadCrumb,
1653+ BreadCrumbTrail,
1654+)
1655+
1656+from dashboard_app.views import index
1657+
1658+from dashboard_app.views.image_reports.forms import (
1659+ ImageReportEditorForm,
1660+ ImageReportChartForm,
1661+ ImageChartFilterForm,
1662+ )
1663+
1664+from dashboard_app.models import (
1665+ ImageReport,
1666+ ImageReportChart,
1667+ ImageChartFilter,
1668+ ImageChartTest,
1669+ ImageChartTestCase,
1670+ Test,
1671+ TestCase,
1672+ TestRunFilter,
1673+ )
1674+
1675+from dashboard_app.views.filters.tables import AllFiltersSimpleTable
1676+
1677+
1678+
1679+@BreadCrumb("Image reports", parent=index)
1680+def image_report_list(request):
1681+
1682+ if request.user.is_authenticated():
1683+ image_reports = ImageReport.objects.all()
1684+ else:
1685+ image_reports = None
1686+
1687+ return render_to_response(
1688+ 'dashboard_app/image_report_list.html', {
1689+ "image_reports": image_reports,
1690+ }, RequestContext(request)
1691+ )
1692+
1693+@BreadCrumb("Image report {name}", parent=image_report_list, needs=['name'])
1694+def image_report_detail(request, name):
1695+ image_report = ImageReport.objects.get(name=name)
1696+
1697+ return render_to_response(
1698+ 'dashboard_app/image_report_detail.html', {
1699+ 'image_report': image_report,
1700+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
1701+ image_report_detail, name=name),
1702+ }, RequestContext(request)
1703+ )
1704+
1705+@BreadCrumb("Add new image report", parent=image_report_list)
1706+@login_required
1707+def image_report_add(request):
1708+ return image_report_form(
1709+ request,
1710+ BreadCrumbTrail.leading_to(image_report_add))
1711+
1712+@BreadCrumb("Update image report {name}", parent=image_report_list,
1713+ needs=['name'])
1714+@login_required
1715+def image_report_edit(request, name):
1716+ image_report = ImageReport.objects.get(name=name)
1717+ return image_report_form(
1718+ request,
1719+ BreadCrumbTrail.leading_to(image_report_edit,
1720+ name=name),
1721+ instance=image_report)
1722+
1723+@BreadCrumb("Publish image report {name}", parent=image_report_list,
1724+ needs=['name'])
1725+@login_required
1726+def image_report_publish(request, name):
1727+ image_report = ImageReport.objects.get(name=name)
1728+ image_report.is_published = True
1729+ image_report.save()
1730+
1731+ return render_to_response(
1732+ 'dashboard_app/image_report_detail.html', {
1733+ 'image_report': image_report,
1734+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
1735+ image_report_detail, name=name),
1736+ }, RequestContext(request)
1737+ )
1738+
1739+@BreadCrumb("Unpublish image report {name}", parent=image_report_list,
1740+ needs=['name'])
1741+@login_required
1742+def image_report_unpublish(request, name):
1743+ image_report = ImageReport.objects.get(name=name)
1744+ image_report.is_published = False
1745+ image_report.save()
1746+
1747+ return render_to_response(
1748+ 'dashboard_app/image_report_detail.html', {
1749+ 'image_report': image_report,
1750+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
1751+ image_report_detail, name=name),
1752+ }, RequestContext(request)
1753+ )
1754+
1755+def image_report_form(request, bread_crumb_trail, instance=None):
1756+
1757+ if request.method == 'POST':
1758+
1759+ form = ImageReportEditorForm(request.user, request.POST,
1760+ instance=instance)
1761+ if form.is_valid():
1762+ image_report = form.save()
1763+ return HttpResponseRedirect(image_report.get_absolute_url())
1764+
1765+ else:
1766+ form = ImageReportEditorForm(request.user, instance=instance)
1767+
1768+ return render_to_response(
1769+ 'dashboard_app/image_report_form.html', {
1770+ 'bread_crumb_trail': bread_crumb_trail,
1771+ 'form': form,
1772+ }, RequestContext(request))
1773+
1774+@BreadCrumb("Image chart details", parent=image_report_list)
1775+def image_chart_detail(request, id):
1776+ image_chart = ImageReportChart.objects.get(id=id)
1777+
1778+ return render_to_response(
1779+ 'dashboard_app/image_report_chart_detail.html', {
1780+ 'image_chart': image_chart,
1781+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
1782+ image_chart_detail, id=id),
1783+ }, RequestContext(request)
1784+ )
1785+
1786+@BreadCrumb("Add new image chart", parent=image_report_list)
1787+@login_required
1788+def image_chart_add(request):
1789+ return image_chart_form(
1790+ request,
1791+ BreadCrumbTrail.leading_to(image_chart_add))
1792+
1793+@BreadCrumb("Update image chart", parent=image_report_list)
1794+@login_required
1795+def image_chart_edit(request, id):
1796+ image_chart = ImageReportChart.objects.get(id=id)
1797+ return image_chart_form(
1798+ request,
1799+ BreadCrumbTrail.leading_to(image_chart_edit,
1800+ id=id),
1801+ instance=image_chart)
1802+
1803+def image_chart_form(request, bread_crumb_trail, instance=None):
1804+
1805+ if request.method == 'POST':
1806+
1807+ form = ImageReportChartForm(request.user, request.POST,
1808+ instance=instance)
1809+ if form.is_valid():
1810+ image_chart = form.save()
1811+ return HttpResponseRedirect(
1812+ image_chart.get_absolute_url())
1813+
1814+ else:
1815+ form = ImageReportChartForm(request.user, instance=instance)
1816+
1817+ if not instance:
1818+ image_report_id = request.GET.get('image_report_id', None)
1819+ else:
1820+ image_report_id = instance.image_report.id
1821+
1822+ filters_table = AllFiltersSimpleTable("all-filters", None)
1823+
1824+ return render_to_response(
1825+ 'dashboard_app/image_report_chart_form.html', {
1826+ 'bread_crumb_trail': bread_crumb_trail,
1827+ 'form': form,
1828+ 'filters_table': filters_table,
1829+ 'image_report_id': image_report_id,
1830+ }, RequestContext(request))
1831+
1832+@BreadCrumb("Image chart add filter", parent=image_report_list)
1833+def image_chart_filter_add(request, id):
1834+ image_chart = ImageReportChart.objects.get(id=id)
1835+ return image_chart_filter_form(
1836+ request,
1837+ BreadCrumbTrail.leading_to(image_chart_filter_add),
1838+ chart_instance=image_chart)
1839+
1840+@BreadCrumb("Update image chart filter", parent=image_report_list)
1841+@login_required
1842+def image_chart_filter_edit(request, id):
1843+ image_chart_filter = ImageChartFilter.objects.get(id=id)
1844+ return image_chart_filter_form(
1845+ request,
1846+ BreadCrumbTrail.leading_to(image_chart_filter_edit, id=id),
1847+ instance=image_chart_filter)
1848+
1849+@BreadCrumb("Image chart add filter", parent=image_report_list)
1850+def image_chart_filter_delete(request, id):
1851+ image_chart_filter = ImageChartFilter.objects.get(id=id)
1852+ url = image_chart_filter.image_chart.get_absolute_url()
1853+ image_chart_filter.delete()
1854+ return HttpResponseRedirect(url)
1855+
1856+def image_chart_filter_form(request, bread_crumb_trail, chart_instance=None,
1857+ instance=None):
1858+
1859+ if instance:
1860+ chart_instance = instance.image_chart
1861+
1862+ if request.method == 'POST':
1863+
1864+ form = ImageChartFilterForm(request.user, request.POST,
1865+ instance=instance)
1866+
1867+ if form.is_valid():
1868+
1869+ chart_filter = form.save()
1870+ aliases = request.POST.getlist('aliases')
1871+
1872+
1873+ if chart_filter.image_chart.chart_type == 'pass/fail':
1874+
1875+ image_chart_tests = Test.objects.filter(
1876+ imagecharttest__image_chart_filter=chart_filter).order_by(
1877+ 'id')
1878+
1879+ tests = form.cleaned_data['image_chart_tests']
1880+
1881+ for index, test in enumerate(tests):
1882+ if test in image_chart_tests:
1883+ chart_test = ImageChartTest.objects.get(
1884+ image_chart_filter=chart_filter, test=test)
1885+ chart_test.name = aliases[index]
1886+ chart_test.save()
1887+ else:
1888+ chart_test = ImageChartTest()
1889+ chart_test.image_chart_filter = chart_filter
1890+ chart_test.test = test
1891+ chart_test.name = aliases[index]
1892+ chart_test.save()
1893+
1894+ for index, chart_test in enumerate(image_chart_tests):
1895+ if chart_test not in tests:
1896+ ImageChartTest.objects.get(
1897+ image_chart_filter=chart_filter,
1898+ test=chart_test).delete()
1899+
1900+ return HttpResponseRedirect(
1901+ chart_filter.image_chart.get_absolute_url())
1902+
1903+ else:
1904+
1905+ image_chart_test_cases = TestCase.objects.filter(
1906+ imagecharttestcase__image_chart_filter=
1907+ chart_filter).order_by('id')
1908+
1909+ test_cases = form.cleaned_data['image_chart_test_cases']
1910+
1911+ for index, test_case in enumerate(test_cases):
1912+ if test_case in image_chart_test_cases:
1913+ chart_test_case = ImageChartTestCase.objects.get(
1914+ image_chart_filter=chart_filter,
1915+ test_case=test_case)
1916+ chart_test_case.name = aliases[index]
1917+ chart_test_case.save()
1918+ else:
1919+ chart_test_case = ImageChartTestCase()
1920+ chart_test_case.image_chart_filter = chart_filter
1921+ chart_test_case.test_case = test_case
1922+ chart_test_case.name = aliases[index]
1923+ chart_test_case.save()
1924+
1925+ for index, chart_test_case in enumerate(
1926+ image_chart_test_cases):
1927+ if chart_test_case not in test_cases:
1928+ ImageChartTestCase.objects.get(
1929+ image_chart_filter=chart_filter,
1930+ test_case=chart_test_case).delete()
1931+
1932+ return HttpResponseRedirect(
1933+ chart_filter.image_chart.get_absolute_url())
1934+
1935+ else:
1936+ form = ImageChartFilterForm(request.user, instance=instance,
1937+ initial={'image_chart': chart_instance})
1938+
1939+ filters_table = AllFiltersSimpleTable("all-filters", None)
1940+
1941+ return render_to_response(
1942+ 'dashboard_app/image_chart_filter_form.html', {
1943+ 'bread_crumb_trail': bread_crumb_trail,
1944+ 'filters_table': filters_table,
1945+ 'image_chart': chart_instance,
1946+ 'instance': instance,
1947+ 'form': form,
1948+ }, RequestContext(request))

Subscribers

People subscribed via source and target branches