diff --git a/combo/apps/dataviz/models.py b/combo/apps/dataviz/models.py index 2fcce5b2..4f8dcdc7 100644 --- a/combo/apps/dataviz/models.py +++ b/combo/apps/dataviz/models.py @@ -30,6 +30,10 @@ from combo.data.library import register_cell_class from combo.utils import get_templated_url, requests +class UnsupportedDataSet(Exception): + pass + + @register_cell_class class Gauge(CellBase): title = models.CharField(_('Title'), max_length=150, blank=True, null=True) @@ -157,11 +161,15 @@ class ChartNgCell(CellBase): def get_cell_extra_context(self, context): ctx = super(ChartNgCell, self).get_cell_extra_context(context) if self.chart_type == 'table': - chart = self.get_chart(raise_if_not_cached=not(context.get('synchronous'))) - ctx['table'] = chart.render_table( - transpose=bool(chart.axis_count == 2), - ) - ctx['table'] = ctx['table'].replace('', '
') + try: + chart = self.get_chart(raise_if_not_cached=not(context.get('synchronous'))) + except UnsupportedDataSet: + ctx['table'] = '

%s

' % _('Unsupported dataset.') + else: + ctx['table'] = chart.render_table( + transpose=bool(chart.axis_count == 2), + ) + ctx['table'] = ctx['table'].replace('
', '
') return ctx def get_chart(self, width=None, height=None, raise_if_not_cached=False): @@ -186,9 +194,18 @@ class ChartNgCell(CellBase): # normalize axis to have a fake axis when there are no dimensions and # always a x axis when there is a single dimension. + data = response['data'] + loop_labels = response['axis'].get('loop') or [] x_labels = response['axis'].get('x_labels') or [] y_labels = response['axis'].get('y_labels') or [] - data = response['data'] + if loop_labels: + if x_labels and y_labels: + # no support for three dimensions + raise UnsupportedDataSet() + if not y_labels: + y_labels = loop_labels + else: + x_labels, y_labels = y_labels, loop_labels if not x_labels and not y_labels: # unidata x_labels = [''] y_labels = [''] diff --git a/combo/apps/dataviz/views.py b/combo/apps/dataviz/views.py index 75c8dae1..54d28314 100644 --- a/combo/apps/dataviz/views.py +++ b/combo/apps/dataviz/views.py @@ -16,9 +16,10 @@ from django.core.exceptions import PermissionDenied from django.http import HttpResponse +from django.utils.translation import ugettext_lazy as _ from combo.utils import get_templated_url, requests -from .models import Gauge, ChartNgCell +from .models import Gauge, ChartNgCell, UnsupportedDataSet def ajax_gauge_count(request, *args, **kwargs): @@ -33,8 +34,21 @@ def dataviz_graph(request, *args, **kwargs): raise PermissionDenied() if not cell.is_visible(request.user): raise PermissionDenied() - chart = cell.get_chart( - width=int(request.GET.get('width', 0)) or None, - height=int(request.GET.get('height', 0)) or int(cell.height) - ) - return HttpResponse(chart.render(), content_type='image/svg+xml') + try: + chart = cell.get_chart( + width=int(request.GET.get('width', 0)) or None, + height=int(request.GET.get('height', 0)) or int(cell.height) + ) + except UnsupportedDataSet: + svg = """ + + %(text)s +""" % {'width': request.GET.get('width', 200), + 'text': _('Unsupported dataset.')} + else: + svg = chart.render() + return HttpResponse(svg, content_type='image/svg+xml') diff --git a/tests/test_dataviz.py b/tests/test_dataviz.py index 558505df..c58151f2 100644 --- a/tests/test_dataviz.py +++ b/tests/test_dataviz.py @@ -8,7 +8,7 @@ from django.contrib.auth.models import User, Group from django.test import override_settings from combo.data.models import Page -from combo.apps.dataviz.models import Gauge, ChartNgCell +from combo.apps.dataviz.models import Gauge, ChartNgCell, UnsupportedDataSet from .test_public import login, normal_user @@ -70,6 +70,25 @@ VISUALIZATION_JSON = [ 'name': 'fourth visualization (no axis)', 'slug': 'fourth', }, + { + 'data-url': 'https://bijoe.example.com/visualization/5/json/', + 'path': 'https://bijoe.example.com/visualization/5/iframe/?signature=123', + 'name': 'fifth visualization (loop/X)', + 'slug': 'fifth', + }, + { + 'data-url': 'https://bijoe.example.com/visualization/6/json/', + 'path': 'https://bijoe.example.com/visualization/6/iframe/?signature=123', + 'name': 'sixth visualization (loop/Y)', + 'slug': 'sixth', + }, + { + 'data-url': 'https://bijoe.example.com/visualization/7/json/', + 'path': 'https://bijoe.example.com/visualization/7/iframe/?signature=123', + 'name': 'seventh visualization (loop/X/Y)', + 'slug': 'seventh', + }, + ] @@ -114,6 +133,48 @@ def bijoe_mock(url, request): 'axis': {} } return {'content': json.dumps(response), 'request': request, 'status_code': 200} + if url.path == '/visualization/5/json/': + response = { + 'format': '1', + 'data': [ + [222, 134, 53], + [122, 114, 33], + ], + 'axis': { + 'x_labels': ['web', 'mail', 'email'], + 'loop': ['foo', 'bar'], + } + } + return {'content': json.dumps(response), 'request': request, 'status_code': 200} + if url.path == '/visualization/6/json/': + response = { + 'format': '1', + 'data': [ + [222, 134, 53], + [122, 114, 33], + ], + 'axis': { + 'y_labels': ['web', 'mail', 'email'], + 'loop': ['foo', 'bar'], + } + } + return {'content': json.dumps(response), 'request': request, 'status_code': 200} + if url.path == '/visualization/7/json/': + response = { + 'format': '1', + 'data': [ + [[222, 134, 53], [122, 114, 33]], + [[222, 134, 53], [122, 114, 33]], + [[222, 134, 53], [122, 114, 33]], + [[222, 134, 53], [122, 114, 33]], + ], + 'axis': { + 'x_labels': ['foo', 'bar'], + 'y_labels': ['web', 'mail', 'email'], + 'loop': ['a', 'b', 'c', 'd'], + } + } + return {'content': json.dumps(response), 'request': request, 'status_code': 200} def test_chartng_cell(app): @@ -186,6 +247,33 @@ def test_chartng_cell(app): assert chart.x_labels == [''] assert chart.raw_series == [([222], {'title': ''})] + # loop/X + cell.data_reference = 'plop:fifth' + cell.save() + chart = cell.get_chart() + assert chart.x_labels == ['web', 'mail', 'email'] + assert chart.raw_series == [ + ([222, 134, 53], {'title': u'foo'}), + ([122, 114, 33], {'title': u'bar'}), + ] + + # loop/Y + cell.data_reference = 'plop:sixth' + cell.save() + chart = cell.get_chart() + assert chart.x_labels == ['web', 'mail', 'email'] + assert chart.raw_series == [ + ([222, 134, 53], {'title': u'foo'}), + ([122, 114, 33], {'title': u'bar'}), + ] + + # loop/X/Y + cell.data_reference = 'plop:seventh' + cell.save() + with pytest.raises(UnsupportedDataSet): + chart = cell.get_chart() + + def test_chartng_cell_view(app, normal_user): page = Page(title='One', slug='index') page.save() @@ -230,6 +318,17 @@ def test_chartng_cell_view(app, normal_user): resp = app.get('/') assert '' in resp.text + # unsupported dataset + cell.data_reference = 'plop:seventh' + cell.save() + resp = app.get('/') + assert 'Unsupported dataset' in resp.text + + cell.chart_type = 'bar' + cell.save() + resp = app.get('/api/dataviz/graph/1/?width=400', status=200) + assert 'Unsupported dataset' in resp.text + def test_chartng_cell_manager(app, admin_user): page = Page(title='One', slug='index') @@ -247,7 +346,10 @@ def test_chartng_cell_manager(app, admin_user): resp = app.get('/manage/pages/%s/' % page.id) assert resp.form['cdataviz_chartngcell-%s-data_reference' % cell.id].options == [ (u'plop:example', True, u'example visualization (X)'), + (u'plop:fifth', False, u'fifth visualization (loop/X)'), (u'plop:fourth', False, u'fourth visualization (no axis)'), (u'plop:second', False, u'second visualization (Y)'), + (u'plop:seventh', False, u'seventh visualization (loop/X/Y)'), + (u'plop:sixth', False, u'sixth visualization (loop/Y)'), (u'plop:third', False, u'third visualization (X/Y)'), ]
222