Projet

Général

Profil

0001-dataviz-add-cells-to-display-data-from-cubes-using-b.patch

Benjamin Dauvergne, 26 novembre 2015 15:28

Télécharger (29,2 ko)

Voir les différences:

Subject: [PATCH] dataviz: add cells to display data from cubes using barcharts
 and HTML tables (#9098)

It contains two new cells: CubesBarChart and CubesTable. You must have a cubes
server targeted by the CUBES_URL setting for enabling the cells.
 combo/apps/dataviz/README                          |  36 +++++
 combo/apps/dataviz/__init__.py                     |  14 ++
 combo/apps/dataviz/forms.py                        |  88 +++++++++++++
 .../migrations/0003_cubesbarchart_cubestable.py    |  64 +++++++++
 combo/apps/dataviz/models.py                       | 129 +++++++++++++++++-
 .../apps/dataviz/static/js/combo.cubes-barchart.js |  89 +++++++++++++
 .../dataviz/templates/combo/cubes-barchart.html    |  12 ++
 .../apps/dataviz/templates/combo/cubes-table.html  |  23 ++++
 combo/apps/dataviz/urls.py                         |   3 +-
 combo/apps/dataviz/utils.py                        | 146 +++++++++++++++++++++
 combo/apps/dataviz/views.py                        |   4 +-
 combo/settings.py                                  |   2 +
 setup.py                                           |   1 +
 13 files changed, 603 insertions(+), 8 deletions(-)
 create mode 100644 combo/apps/dataviz/README
 create mode 100644 combo/apps/dataviz/forms.py
 create mode 100644 combo/apps/dataviz/migrations/0003_cubesbarchart_cubestable.py
 create mode 100644 combo/apps/dataviz/static/js/combo.cubes-barchart.js
 create mode 100644 combo/apps/dataviz/templates/combo/cubes-barchart.html
 create mode 100644 combo/apps/dataviz/templates/combo/cubes-table.html
 create mode 100644 combo/apps/dataviz/utils.py
combo/apps/dataviz/README
1
Data visualization cells
2
========================
3

  
4
Gauge cell
5
----------
6

  
7
FIXME
8

  
9
Cubes cells
10
-----------
11

  
12
Those cells are activated by setting the CUBES_URL setting to the root URL of a
13
Cubes[1] 1.1 server.
14

  
15
Cubes server is accessed using the requests library, you can define custom
16
parameters for the requests.get() calls by setting CUBES_REQUESTS_PARAMS, for
17
example to disable SSL certificate validation:
18

  
19
    CUBES_REQUESTS_PARAMS = {'verify': False}
20

  
21
The CubesBarChart cell use the Chart.js library (through the XStatic-Chart.js
22
pacakge) to render bar charts of the selected aggregated data. The y axis
23
measure the aggregate which is a computed, the x axis is the dimension chosen
24
for the first drill-down axis. The second drilldown axis will be used to
25
generate multiple datasets, one by dimension point, i.e. result generated for
26
the second axis will be grouped along the first drilldown axis.
27

  
28
Ordering by drilldown axis is automatically done using implicit ordering defined
29
by the Cubes model.
30

  
31
The CubesTable render the same data as CubesBarChart but by using HTML tables.
32
The first drilldown axis is used for the row headers and the second drilldown
33
axis for the column headers. By using the two axis at the same time you can make
34
pivot tables.
35

  
36
[1]: https://pythonhosted.org/cubes/
combo/apps/dataviz/__init__.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import re
18

  
17 19
import django.apps
20
from django.core import checks
21
from django.conf import settings
22

  
18 23

  
19 24
class AppConfig(django.apps.AppConfig):
20 25
    name = 'combo.apps.dataviz'
......
23 28
        from . import urls
24 29
        return urls.urlpatterns
25 30

  
31
    def ready(self):
32
        @checks.register('settings')
33
        def check_settings(**kwargs):
34
            # Check if CUBES_URL is a proper URL string
35
            if (getattr(settings, 'CUBES_URL', None) is not None
36
                and (not isinstance(settings.CUBES_URL, str)
37
                     or not re.match(r'https?://', settings.CUBES_URL))):
38
                yield checks.Error('settings.CUBES_URL must be an HTTP URL')
39

  
26 40
default_app_config = 'combo.apps.dataviz.AppConfig'
combo/apps/dataviz/forms.py
1
# combo - content management system
2
# Copyright (C) 2015  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import json
18

  
19
from django.utils.translation import ugettext_lazy as _
20
from django import forms
21
from django.conf import settings
22
from django.core.exceptions import ValidationError
23

  
24
from .models import CubesBarChart
25
from .utils import get_cubes, get_cube, get_drilldown
26

  
27

  
28
class CubesBarChartForm(forms.ModelForm):
29
    EMPTY = [(u'', _('None'))]
30

  
31
    class Meta:
32
        model = CubesBarChart
33
        fields = ('title', 'url', 'cube', 'aggregate1', 'drilldown1', 'drilldown2',
34
                  'other_parameters')
35

  
36
    def __init__(self, *args, **kwargs):
37
        super(CubesBarChartForm, self).__init__(*args, **kwargs)
38
        for field in ('cube', 'aggregate1', 'drilldown1', 'drilldown2'):
39
            self.fields[field] = forms.ChoiceField(
40
                label=self.fields[field].label,
41
                initial=self.fields[field].initial,
42
                required=False,
43
                choices=self.EMPTY)
44
        if getattr(settings, 'CUBES_URL', None):
45
            cube_choices = self.get_cubes_choices()
46
            if cube_choices:
47
                self.fields['cube'].choices = cube_choices
48
            aggregate1_choices = self.get_aggregate_choices()
49
            if aggregate1_choices:
50
                self.fields['aggregate1'].choices = aggregate1_choices
51
            drilldown_choices = self.get_drilldown_choices()
52
            if drilldown_choices:
53
                self.fields['drilldown1'].choices = drilldown_choices
54
                self.fields['drilldown2'].choices = drilldown_choices
55

  
56
    def clean_other_parameters(self):
57
        other_parameters = self.cleaned_data['other_parameters']
58
        if other_parameters:
59
            try:
60
                decoded = json.loads(other_parameters)
61
                assert isinstance(decoded, dict)
62
                for key, value in decoded.iteritems():
63
                    assert isinstance(key, unicode)
64
                    assert isinstance(value, unicode)
65
            except (ValueError, AssertionError):
66
                raise ValidationError(_('Other parameter must be a JSON object containing only '
67
                                        'strings'))
68
        return other_parameters
69

  
70
    def get_cubes_choices(self):
71
        cubes = get_cubes()
72
        return self.EMPTY + [(cube['name'], cube.get('label')) for cube in cubes]
73

  
74
    def get_aggregate_choices(self):
75
        cube = self.instance.cube
76
        if cube:
77
            cube = get_cube(cube)
78
            if cube:
79
                return self.EMPTY + [(ag['name'], ag['label']) for ag in cube.get('aggregates', [])]
80
        return []
81

  
82
    def get_drilldown_choices(self):
83
        cube = self.instance.cube
84
        if cube:
85
            choices = get_drilldown(cube)
86
            if choices:
87
                return self.EMPTY + choices
88
        return []
combo/apps/dataviz/migrations/0003_cubesbarchart_cubestable.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import models, migrations
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('auth', '0001_initial'),
11
        ('data', '0012_auto_20151029_1535'),
12
        ('dataviz', '0002_gauge_jsonp_data_source'),
13
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='CubesBarChart',
18
            fields=[
19
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
20
                ('placeholder', models.CharField(max_length=20)),
21
                ('order', models.PositiveIntegerField()),
22
                ('slug', models.SlugField(verbose_name='Slug', blank=True)),
23
                ('public', models.BooleanField(default=True, verbose_name='Public')),
24
                ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
25
                ('title', models.CharField(max_length=150, null=True, verbose_name='Title', blank=True)),
26
                ('url', models.URLField(max_length=150, null=True, verbose_name='URL', blank=True)),
27
                ('cube', models.CharField(max_length=256, null=True, verbose_name='Cube', blank=True)),
28
                ('aggregate1', models.CharField(max_length=64, null=True, verbose_name='Aggregate', blank=True)),
29
                ('drilldown1', models.CharField(max_length=64, null=True, verbose_name='Drilldown 1', blank=True)),
30
                ('drilldown2', models.CharField(max_length=64, null=True, verbose_name='Drilldown 2', blank=True)),
31
                ('other_parameters', models.TextField(null=True, verbose_name='Other parameters', blank=True)),
32
                ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
33
                ('page', models.ForeignKey(to='data.Page')),
34
            ],
35
            options={
36
                'verbose_name': 'Cubes Barchart',
37
            },
38
            bases=(models.Model,),
39
        ),
40
        migrations.CreateModel(
41
            name='CubesTable',
42
            fields=[
43
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
44
                ('placeholder', models.CharField(max_length=20)),
45
                ('order', models.PositiveIntegerField()),
46
                ('slug', models.SlugField(verbose_name='Slug', blank=True)),
47
                ('public', models.BooleanField(default=True, verbose_name='Public')),
48
                ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
49
                ('title', models.CharField(max_length=150, null=True, verbose_name='Title', blank=True)),
50
                ('url', models.URLField(max_length=150, null=True, verbose_name='URL', blank=True)),
51
                ('cube', models.CharField(max_length=256, null=True, verbose_name='Cube', blank=True)),
52
                ('aggregate1', models.CharField(max_length=64, null=True, verbose_name='Aggregate', blank=True)),
53
                ('drilldown1', models.CharField(max_length=64, null=True, verbose_name='Drilldown 1', blank=True)),
54
                ('drilldown2', models.CharField(max_length=64, null=True, verbose_name='Drilldown 2', blank=True)),
55
                ('other_parameters', models.TextField(null=True, verbose_name='Other parameters', blank=True)),
56
                ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
57
                ('page', models.ForeignKey(to='data.Page')),
58
            ],
59
            options={
60
                'verbose_name': 'Cubes Table',
61
            },
62
            bases=(models.Model,),
63
        ),
64
    ]
combo/apps/dataviz/models.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import json
18
from collections import OrderedDict
19

  
17 20
from django.core.urlresolvers import reverse
18 21
from django.db import models
19 22
from django.utils.translation import ugettext_lazy as _
23
from django.conf import settings
20 24

  
21 25
from combo.data.models import CellBase
22 26
from combo.data.library import register_cell_class
......
26 30
class Gauge(CellBase):
27 31
    title = models.CharField(_('Title'), max_length=150, blank=True, null=True)
28 32
    url = models.URLField(_('URL'), max_length=150, blank=True, null=True)
29
    data_source = models.CharField(_('Data Source'), max_length=150,
30
            blank=True, null=True)
31
    jsonp_data_source = models.BooleanField(_('Use JSONP to get data'),
32
            default=True)
33
    data_source = models.CharField(_('Data Source'), max_length=150, blank=True, null=True)
34
    jsonp_data_source = models.BooleanField(_('Use JSONP to get data'), default=True)
33 35
    max_value = models.PositiveIntegerField(_('Max Value'), blank=True, null=True)
34 36

  
35 37
    template_name = 'combo/gauge-cell.html'
......
55 57
                'data_source_url': data_source_url,
56 58
                'jsonp': self.jsonp_data_source,
57 59
                }
60

  
61

  
62
class BaseCubesChart(CellBase):
63
    title = models.CharField(_('Title'), max_length=150, blank=True, null=True)
64
    url = models.URLField(_('URL'), max_length=150, blank=True, null=True)
65
    cube = models.CharField(verbose_name=_('Cube'), max_length=256, blank=True, null=True)
66
    aggregate1 = models.CharField(verbose_name=_('Aggregate'), max_length=64, blank=True, null=True)
67
    drilldown1 = models.CharField(verbose_name=_('Drilldown 1'), max_length=64, blank=True,
68
                                  null=True)
69
    drilldown2 = models.CharField(verbose_name=_('Drilldown 2'), max_length=64, blank=True,
70
                                  null=True)
71
    other_parameters = models.TextField(verbose_name=_('Other parameters'), blank=True, null=True)
72

  
73
    class Meta:
74
        abstract = True
75

  
76
    @classmethod
77
    def is_enabled(self):
78
        return bool(getattr(settings, 'CUBES_URL', None))
79

  
80
    def get_additional_label(self):
81
        return self.title
82

  
83
    def get_default_form_class(self):
84
        from .forms import CubesBarChartForm
85
        return CubesBarChartForm
86

  
87
    def get_cell_extra_context(self):
88
        return {
89
            'cell': self,
90
            'title': self.title,
91
            'url': self.url,
92
            'aggregate': self.get_aggregate(),
93
        }
94

  
95
    def get_aggregate(self):
96
        '''Get aggregate defined by chosen cube and the two drildown paths, request ordering of the
97
           data by natural order of each axis.'''
98
        from .utils import get_aggregate, get_cube, compute_levels
99
        aggregate = get_aggregate(name=self.cube,
100
                                  aggregate1=self.aggregate1,
101
                                  drilldown1=self.drilldown1,
102
                                  drilldown2=self.drilldown2,
103
                                  other_parameters=(json.loads(self.other_parameters) if
104
                                                    self.other_parameters else None))
105
        cube = get_cube(self.cube)
106
        if not aggregate or not cube:
107
            return
108

  
109
        label_refs1 = []
110
        key_refs1 = []
111
        if self.drilldown1:
112
            compute_levels(cube, self.drilldown1, label_refs=label_refs1, key_refs=key_refs1)
113
        key_refs2 = []
114
        label_refs2 = []
115
        if self.drilldown2:
116
            compute_levels(cube, self.drilldown2, label_refs=label_refs2, key_refs=key_refs2)
117
        for ag in cube['aggregates']:
118
            if ag['name'] != self.aggregate1:
119
                continue
120
            break
121

  
122
        def cell_ref(cell, refs):
123
            return tuple(cell[ref] for ref in refs)
124

  
125
        keys1 = OrderedDict()
126
        labels = OrderedDict()
127
        datasets = OrderedDict()
128

  
129
        for cell in aggregate['cells']:
130
            label1 = u' / '.join(map(unicode, cell_ref(cell, label_refs1)))
131
            key1 = cell_ref(cell, key_refs1)
132
            labels[key1] = label1
133
            keys1[key1] = 1
134
            if key_refs2:
135
                label2 = u' / '.join(map(unicode, cell_ref(cell, label_refs2)))
136
                key2 = cell_ref(cell, key_refs2)
137
            else:
138
                label2 = ''
139
                key2 = 1
140
            dataset = datasets.setdefault(key2, {'label': label2,
141
                                                 'data': OrderedDict()})
142
            value = cell[self.aggregate1]
143
            dataset['data'][key1] = value
144
        for dataset in datasets.itervalues():
145
            dataset['data'] = [dataset['data'].get(key, 0) for key in keys1]
146

  
147
        return {
148
            'labels': labels.values(),
149
            'datasets': [{
150
                'label': dataset['label'],
151
                'data': dataset['data'],
152
            } for dataset in datasets.itervalues()]
153
        }
154

  
155

  
156
@register_cell_class
157
class CubesBarChart(BaseCubesChart):
158
    template_name = 'combo/cubes-barchart.html'
159

  
160
    class Media:
161
        js = ('xstatic/Chart.min.js', 'js/combo.cubes-barchart.js')
162

  
163
    class Meta:
164
        verbose_name = _('Cubes Barchart')
165

  
166
    def get_cell_extra_context(self):
167
        ctx = super(CubesBarChart, self).get_cell_extra_context()
168
        # Need JSON serialization to pass data to Chart.js
169
        ctx['json_aggregate'] = json.dumps(ctx['aggregate'])
170
        return ctx
171

  
172

  
173
@register_cell_class
174
class CubesTable(BaseCubesChart):
175
    template_name = 'combo/cubes-table.html'
176

  
177
    class Meta:
178
        verbose_name = _('Cubes Table')
combo/apps/dataviz/static/js/combo.cubes-barchart.js
1
$(function() {
2
  var Colors = {};
3

  
4
  Colors.spaced_hsla = function (i, n, s, l, a) {
5
    var h = 360 * i/n;
6
    return "hsla(" + h.toString() + ', ' + s.toString() + '%, ' + l.toString() + '%, ' + a.toString() + ')';
7
  }
8

  
9
  $('.combo-cube-aggregate').each(function(idx, elem) {
10
     var cube_url = $(elem).data('cube-url');
11
     var aggregate_url = $(elem).data('aggregate-url');
12
     var model = null;
13
     var ctx = $('canvas', elem)[0].getContext("2d");
14
     var id = $(elem).data('combo-cube-aggregate-id');
15

  
16
     var option = {
17
        //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
18
        scaleBeginAtZero : true,
19

  
20
        //Boolean - Whether grid lines are shown across the chart
21
        scaleShowGridLines : true,
22

  
23
        //String - Colour of the grid lines
24
        scaleGridLineColor : "rgba(0,0,0,.05)",
25

  
26
        //Number - Width of the grid lines
27
        scaleGridLineWidth : 1,
28

  
29
        //Boolean - Whether to show horizontal lines (except X axis)
30
        scaleShowHorizontalLines: true,
31

  
32
        //Boolean - Whether to show vertical lines (except Y axis)
33
        scaleShowVerticalLines: true,
34

  
35
        //Boolean - If there is a stroke on each bar
36
        barShowStroke : true,
37

  
38
        //Number - Pixel width of the bar stroke
39
        barStrokeWidth : 2,
40

  
41
        //Number - Spacing between each of the X value sets
42
        barValueSpacing : 5,
43

  
44
        //Number - Spacing between data sets within X values
45
        barDatasetSpacing : 1,
46

  
47
        //String - A legend template
48
        legendTemplate : "ul",
49
        multiTooltipTemplate: "<%= datasetLabel %>: <%= value %>",
50
        responsive: true,
51
     }
52
     var data = window['combo_cube_aggregate_' + id];
53
     // Set one color by dataset
54
     var n = data.datasets.length;
55
     for (var i = 0; i < n; i++) {
56
       var dataset = data.datasets[i];
57
       $.extend(dataset, {
58
         fillColor: Colors.spaced_hsla(i, n, 100, 30, 0.5),
59
         strokeColor: Colors.spaced_hsla(i, n, 100, 30, 0.75),
60
         highlightFill: Colors.spaced_hsla(i, n, 100, 30, 0.75),
61
         highlightStroke: Colors.spaced_hsla(i, n, 100, 30, 1)
62
       })
63
     }
64
     var clone = function(obj){
65
       var objClone = {};
66
       for (var key in obj) {
67
         if (obj.hasOwnProperty(key)) objClone[key] = obj[key];
68
       };
69
       return objClone;
70
     }
71
     var chart = new Chart(ctx).Bar(data, option);
72
     if (chart.datasets.length == 1) {
73
       // Set one color by bar 
74
       var n = chart.datasets[0].bars.length;
75
       for (var i = 0; i < n; i++) {
76
         var bar = chart.datasets[0].bars[i];
77
         $.extend(bar, {
78
           fillColor: Colors.spaced_hsla(i, n, 100, 30, 0.5),
79
           strokeColor: Colors.spaced_hsla(i, n, 100, 30, 0.75),
80
           highlightFill: Colors.spaced_hsla(i, n, 100, 30, 0.75),
81
           highlightStroke: Colors.spaced_hsla(i, n, 100, 30, 1)
82
         })
83
         bar['_saved'] = clone(bar);
84
         bar.update();
85
       }
86
     }
87
     window.chart = chart;
88
  })
89
})
combo/apps/dataviz/templates/combo/cubes-barchart.html
1
<script>
2
    var combo_cube_aggregate_{{ cell.id }} = {{ json_aggregate|safe }};
3
</script>
4
<div
5
     class="combo-cube-aggregate"
6
     data-combo-cube-aggregate-id="{{ cell.id }}">
7
  {% if title %}
8
    {% if url %}<a href="{{url}}">{% endif %}{{title}}{% if url %}</a>{% endif %}
9
  {% endif %}
10
  <canvas style="width: 100%;">
11
  </canvas>
12
</div>
combo/apps/dataviz/templates/combo/cubes-table.html
1
<table class="combo-cube-table">
2
  {% if title %}
3
    <caption>
4
    {% if url %}<a href="{{url}}">{% endif %}{{title}}{% if url %}</a>{% endif %}
5
    </caption>
6
  {% endif %}
7
  <thead>
8
    <th></th>
9
    {% for label in aggregate.labels %}
10
      <td>{{ label }}</td>
11
    {% endfor %}
12
  </thead>
13
  <tbody>
14
    {% for dataset in aggregate.datasets %}
15
      <tr> 
16
        <th>{{ dataset.label }}</th>
17
        {% for value in dataset.data %}
18
          <td>{{ value }}</td>
19
        {% endfor %}
20
      </tr>
21
    {% endfor %}
22
  </tbody>
23
</table>
combo/apps/dataviz/urls.py
18 18

  
19 19
from .views import ajax_gauge_count
20 20

  
21
urlpatterns = patterns('',
21
urlpatterns = patterns(
22
    '',
22 23
    url(r'^ajax/gauge-count/(?P<cell>[\w_-]+)/$',
23 24
        ajax_gauge_count, name='combo-ajax-gauge-count'),
24 25
)
combo/apps/dataviz/utils.py
1
# combo - content management system
2
# Copyright (C) 2015  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import requests
18
from requests.exceptions import RequestException
19
import urlparse
20

  
21
from django.conf import settings
22

  
23

  
24
def get_requests_params():
25
    return getattr(settings, 'CUBES_REQUEST_PARAMS', {})
26

  
27

  
28
def get_cubes():
29
    try:
30
        r = requests.get(urlparse.urljoin(settings.CUBES_URL, 'cubes'), **get_requests_params)
31
    except RequestException:
32
        return []
33
    try:
34
        return r.json()
35
    except ValueError:
36
        return []
37

  
38

  
39
def get_cube(name):
40
    model_url = urlparse.urljoin(settings.CUBES_URL, 'cube/%s/model' % name)
41
    try:
42
        r = requests.get(model_url, **get_requests_params)
43
    except RequestException:
44
        return None
45
    try:
46
        return r.json()
47
    except ValueError:
48
        return None
49

  
50

  
51
def get_drilldown(name):
52
    cube = get_cube(name)
53
    if not cube:
54
        return []
55
    l = []
56
    for dimension in cube.get('dimensions', []):
57
        dim_name = dimension['name']
58
        dim_label = dimension.get('label') or dim_name
59
        if dimension.get('levels'):
60
            levels = {}
61
            for level in dimension['levels']:
62
                levels[level['name']] = level.get('label') or level['name']
63
            if dimension.get('hierarchies'):
64
                for hierarchy in dimension['hierarchies']:
65
                    h_name = hierarchy['name']
66
                    h_label = hierarchy.get('label') or h_name
67
                    if h_name == 'default':
68
                        h_label = ''
69
                    for level in hierarchy['levels']:
70
                        labels = filter(None, [dim_label, h_label, levels[level]])
71
                        label = ' - '.join(labels)
72
                        name = '%s@%s:%s' % (dim_name, h_name, level)
73
                        l.append((name, label))
74
            else:
75
                raise NotImplementedError
76
        else:
77
            l.append((dim_name, dim_label))
78
    return l
79

  
80

  
81
def compute_levels(cube, drilldown, key_refs=None, label_refs=None):
82
    from .utils import get_attribute_ref
83
    dim = drilldown.split('@')[0]
84
    hier = drilldown.split('@')[1].split(':')[0]
85
    lev = drilldown.split(':')[1]
86

  
87
    for dimension in cube['dimensions']:
88
        if dimension['name'] != dim:
89
            continue
90
        level_label_refs = {}
91
        level_key_refs = {}
92
        for level in dimension['levels']:
93
            level_key_refs[level['name']] = get_attribute_ref(level, level['key'])
94
            level_label_refs[level['name']] = get_attribute_ref(level, level['label_attribute'])
95
        for hierarchy in dimension['hierarchies']:
96
            if hierarchy['name'] != hier:
97
                continue
98
            for level in hierarchy['levels']:
99
                if key_refs is not None:
100
                    key_refs.append(level_key_refs[level])
101
                if label_refs is not None:
102
                    label_refs.append(level_label_refs[level])
103
                if level == lev:
104
                    break
105
            break
106
        break
107

  
108

  
109
def get_aggregate(name, aggregate1, drilldown1, drilldown2, other_parameters=None):
110
    if not name:
111
        return None
112
    cube = get_cube(name)
113
    aggregate_url = urlparse.urljoin(settings.CUBES_URL, 'cube/%s/aggregate' % name)
114
    if not aggregate1:
115
        return None
116
    try:
117
        params = {'aggregate': aggregate1}
118
        drilldowns = []
119
        key_refs = []
120
        if drilldown1:
121
            compute_levels(cube, drilldown1, key_refs=key_refs)
122
            drilldowns.append(drilldown1)
123
        if drilldown2:
124
            compute_levels(cube, drilldown2, key_refs=key_refs)
125
            drilldowns.append(drilldown2)
126
        if drilldowns:
127
            params['drilldown'] = drilldowns
128

  
129
        if key_refs:
130
            params['order'] = key_refs
131
        if other_parameters:
132
            params.update(other_parameters)
133

  
134
        r = requests.get(aggregate_url, params=params, **get_requests_params)
135
    except RequestException:
136
        return None
137
    try:
138
        return r.json()
139
    except ValueError:
140
        return None
141

  
142

  
143
def get_attribute_ref(level, name):
144
    for attribute in level['attributes']:
145
        if attribute['name'] == name:
146
            return attribute['ref']
combo/apps/dataviz/views.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import json
18 17
import requests
19 18

  
20
from django.conf import settings
21
from django.http import Http404, HttpResponse
19
from django.http import HttpResponse
22 20

  
23 21
from .models import Gauge
24 22

  
combo/settings.py
63 63
    'combo.apps.wcs',
64 64
    'combo.apps.publik',
65 65
    'combo.apps.family',
66
    'combo.apps.dataviz',
67
    'xstatic.pkg.chart_js',
66 68
)
67 69

  
68 70
INSTALLED_APPS = plugins.register_plugins_apps(INSTALLED_APPS)
setup.py
109 109
        'feedparser',
110 110
        'django-jsonfield',
111 111
        'requests',
112
        'XStatic-Chart.js',
112 113
        ],
113 114
    zip_safe=False,
114 115
    cmdclass={
115
-