add support for loop, horizontal and vertical drilldowns and row/columns totals (fixes #15168)
- allow filtering on date-like generated dimensions.
This commit is contained in:
parent
b6b01da8ee
commit
ab947ce0a0
|
@ -164,6 +164,7 @@ class Dimension(Base):
|
|||
@property
|
||||
def dimensions(self):
|
||||
if self.type == 'date':
|
||||
filter_value = self.filter_value or self.value
|
||||
return [
|
||||
self,
|
||||
Dimension(
|
||||
|
@ -172,12 +173,16 @@ class Dimension(Base):
|
|||
name=self.name + '__year',
|
||||
type='integer',
|
||||
join=self.join,
|
||||
filter_value='EXTRACT(year from %s)::integer' % filter_value,
|
||||
filter_in_join=self.filter_in_join,
|
||||
value='EXTRACT(year from %s)::integer' % self.value,
|
||||
filter=False),
|
||||
Dimension(
|
||||
label=u'mois (%s)' % self.label,
|
||||
name=self.name + '__month',
|
||||
type='integer',
|
||||
filter_value='EXTRACT(month from %s)' % filter_value,
|
||||
filter_in_join=self.filter_in_join,
|
||||
join=self.join,
|
||||
value='EXTRACT(month from %s)' % self.value,
|
||||
value_label='to_char(date_trunc(\'month\', %s), \'TMmonth\')' % self.value,
|
||||
|
@ -190,6 +195,8 @@ class Dimension(Base):
|
|||
name=self.name + '__dow',
|
||||
type='integer',
|
||||
join=self.join,
|
||||
filter_value='EXTRACT(dow from %s)' % filter_value,
|
||||
filter_in_join=self.filter_in_join,
|
||||
value='EXTRACT(dow from %s)' % self.value,
|
||||
order_by='(EXTRACT(dow from %s) + 6)::integer %% 7' % self.value,
|
||||
value_label='to_char(date_trunc(\'week\', current_date)::date '
|
||||
|
@ -200,6 +207,9 @@ class Dimension(Base):
|
|||
name=self.name + '__isoweek',
|
||||
type='integer',
|
||||
join=self.join,
|
||||
filter_value='EXTRACT(isoyear from %s) || \'S\' || EXTRACT(week from %s)'
|
||||
% (filter_value, filter_value),
|
||||
filter_in_join=self.filter_in_join,
|
||||
value='EXTRACT(isoyear from %s) || \'S\' || EXTRACT(week from %s)'
|
||||
% (self.value, self.value),
|
||||
group_by='EXTRACT(isoyear from %s), EXTRACT(week from %s)' % (self.value,
|
||||
|
|
|
@ -156,6 +156,10 @@ function draw_piechart(canvas, dimensions, measures, measure, data) {
|
|||
var n = dimensions.length;
|
||||
var option = $.extend({}, defaul_cn_options);
|
||||
var ctx = canvas.getContext("2d");
|
||||
var dimension_labels = [];
|
||||
for (var i = 0; i < dimensions.length; i++) {
|
||||
dimension_labels.push(dimensions[i].label);
|
||||
}
|
||||
|
||||
for (var j = 0; j < measures.length; j++) {
|
||||
if (measures[j].name == measure) {
|
||||
|
@ -242,6 +246,10 @@ function draw_barchart(canvas, dimensions, measures, measure, data) {
|
|||
var n = dimensions.length;
|
||||
var option = $.extend({}, defaul_cn_options);
|
||||
var ctx = canvas.getContext("2d");
|
||||
var dimension_labels = [];
|
||||
for (var i = 0; i < dimensions.length; i++) {
|
||||
dimension_labels.push(dimensions[i].label);
|
||||
}
|
||||
|
||||
for (var j = 0; j < measures.length; j++) {
|
||||
if (measures[j].name == measure) {
|
||||
|
@ -309,7 +317,6 @@ function draw_barchart(canvas, dimensions, measures, measure, data) {
|
|||
}
|
||||
}
|
||||
}
|
||||
console.log(data);
|
||||
|
||||
for (var i = 0; i < n; i++) {
|
||||
var row = data[i];
|
||||
|
|
|
@ -29,13 +29,17 @@
|
|||
<input type="submit" value="ODS" name="ods" id="ods"/>
|
||||
<h3>Représentation</h3>
|
||||
{% include "bijoe/field.html" with field=form.representation %}
|
||||
<h3>Mesure(s)</h3>
|
||||
{% include "bijoe/field.html" with field=form.measures %}
|
||||
<h3>Regroupement(s)</h3>
|
||||
{% include "bijoe/field.html" with field=form.drilldown %}
|
||||
<h3>Répétition</h3>
|
||||
{% include "bijoe/field.html" with field=form.loop %}
|
||||
<h3>Mesure</h3>
|
||||
{% include "bijoe/field.html" with field=form.measure %}
|
||||
<h3>Regroupement horizontal</h3>
|
||||
{% include "bijoe/field.html" with field=form.drilldown_x %}
|
||||
<h3>Regroupement vertical</h3>
|
||||
{% include "bijoe/field.html" with field=form.drilldown_y %}
|
||||
<h3>Filtre(s)</h3>
|
||||
{% for field in form %}
|
||||
{% if not field.is_hidden and field.name != "measures" and field.name != "drilldown" and field.name != "representation" %}
|
||||
{% if not field.is_hidden and field.name != "loop" and field.name != "measure" and field.name != "drilldown_x" and field.name != "drilldown_y" and field.name != "representation" %}
|
||||
{% include "bijoe/filter_field.html" with field=field %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -1,28 +1,24 @@
|
|||
<script>
|
||||
var data = {{ json|safe }};
|
||||
var dimensions = {{ drilldown_json|safe }};
|
||||
var measures = {{ measures_json|safe }};
|
||||
var dimension_labels = [];
|
||||
for (var i = 0; i < dimensions.length; i++) {
|
||||
dimension_labels.push(dimensions[i].label);
|
||||
}
|
||||
</script>
|
||||
|
||||
{% for measure in visualization.measures %}
|
||||
<a href="#" target="none" class="bijoe-button bijoe-png-button">PNG</a>
|
||||
<canvas id="canvas-{{ measure.name }}"></canvas>
|
||||
<script>
|
||||
$(function () {
|
||||
var canvas = $('#canvas-{{ measure.name }}')[0];
|
||||
if ("{{ measure.name }}" == "percent") {
|
||||
draw_piechart(canvas, dimensions, measures, "{{ measure.name }}", data);
|
||||
} else {
|
||||
draw_barchart(canvas, dimensions, measures, "{{ measure.name }}", data);
|
||||
/* Allow getting a PNG without using 'Save image as' */
|
||||
$(".bijoe-png-button").on('click', function() {
|
||||
this.href = toDataURL($(this).next("canvas")[0], "graph.png");
|
||||
})
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% for table in visualization %}
|
||||
<h2>{{ table.table_title }}</h2>
|
||||
|
||||
<a href="#" target="none" class="bijoe-button bijoe-png-button">PNG</a>
|
||||
<canvas id="canvas-{{ forloop.counter }}"></canvas>
|
||||
<script>
|
||||
$(function () {
|
||||
{{ table.javascript }}
|
||||
setTimeout(function () {
|
||||
var canvas = $('#canvas-{{ forloop.counter }}')[0];
|
||||
if (measure.name == "percent") {
|
||||
draw_piechart(canvas, drilldown, [measure], measure.name, data);
|
||||
} else {
|
||||
draw_barchart(canvas, drilldown, [measure], measure.name, data);
|
||||
/* Allow getting a PNG without using 'Save image as' */
|
||||
$(".bijoe-png-button").on('click', function() {
|
||||
this.href = toDataURL($(this).next("canvas")[0], "graph.png");
|
||||
})
|
||||
}
|
||||
}, 500 * {{ forloop.counter }});
|
||||
});
|
||||
</script>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,27 +1,25 @@
|
|||
{% load i18n %}
|
||||
|
||||
<table class="bijoe-table main">
|
||||
<thead>
|
||||
{% for dimension in visualization.drilldown %}
|
||||
<th class="bijoe-drilldown"><span>{{ dimension.label.capitalize }}</span></th>
|
||||
{% endfor %}
|
||||
{% for measure in visualization.measures %}
|
||||
<th class="bijoe-measure"><span>{{ measure.label.capitalize }}</span></th>
|
||||
{% endfor %}
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in visualization.grouped_data %}
|
||||
<tr>
|
||||
{% for cell, count in row %}
|
||||
<td {% if count > 1 %}rowspan="{{ count }}"{% endif %} {% if count > 0 %}class="bijoe-drilldown"{% elif count == 0 %}class="bijoe-measure"{% endif %}>
|
||||
{% if count == 0 %}
|
||||
{% if cell.value == None %}{% trans "None" %}{% else %}{{ cell.value }}{% endif %}
|
||||
{% else %}
|
||||
{% if cell == None %}{% trans "None" %}{% else %}{{ cell }}{% endif %}
|
||||
{% endif %}
|
||||
{% if visualization.loop %}
|
||||
<h2>{{ visualization.title }}</h2>
|
||||
{% endif %}
|
||||
|
||||
{% for table in visualization %}
|
||||
<table class="bijoe-table main">
|
||||
<caption>{{ table.table_title }}</caption>
|
||||
|
||||
<tbody>
|
||||
{% for row in table.table %}
|
||||
<tr>
|
||||
{% for value in row %}
|
||||
<td>
|
||||
{% if value == None %}0{% else %}{{ value }}{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
|
||||
</table>
|
||||
{% endfor %}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.forms import ModelForm, TextInput
|
||||
|
@ -24,12 +25,13 @@ from django.conf import settings
|
|||
try:
|
||||
from django_select2.forms import Select2MultipleWidget
|
||||
|
||||
def build_select2_widget():
|
||||
def build_select2_multiple_widget():
|
||||
return Select2MultipleWidget()
|
||||
|
||||
except ImportError:
|
||||
from django_select2.widgets import Select2MultipleWidget
|
||||
|
||||
def build_select2_widget():
|
||||
def build_select2_multiple_widget():
|
||||
return Select2MultipleWidget(select2_options={'width': '100%'})
|
||||
|
||||
|
||||
|
@ -152,7 +154,16 @@ class CubeForm(forms.Form):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.cube = cube = kwargs.pop('cube')
|
||||
super(CubeForm, self).__init__(*args, **kwargs)
|
||||
self.base_fields = self.base_fields.copy()
|
||||
|
||||
dimension_choices = [('', '')] + [
|
||||
(dimension.name, dimension.label)
|
||||
for dimension in cube.dimensions if dimension.type not in ('datetime', 'date')]
|
||||
# loop
|
||||
self.base_fields['loop'] = forms.ChoiceField(
|
||||
label=_('Loop by'),
|
||||
choices=dimension_choices,
|
||||
required=False)
|
||||
|
||||
# filters
|
||||
for dimension in cube.dimensions:
|
||||
|
@ -160,24 +171,41 @@ class CubeForm(forms.Form):
|
|||
continue
|
||||
field_name = 'filter__%s' % dimension.name
|
||||
if dimension.type == 'date':
|
||||
self.fields[field_name] = DateRangeField(
|
||||
self.base_fields[field_name] = DateRangeField(
|
||||
label=dimension.label.capitalize(), required=False)
|
||||
else:
|
||||
self.fields[field_name] = forms.MultipleChoiceField(
|
||||
self.base_fields[field_name] = forms.MultipleChoiceField(
|
||||
label=dimension.label.capitalize(),
|
||||
choices=dimension.members,
|
||||
required=False,
|
||||
widget=build_select2_widget())
|
||||
widget=build_select2_multiple_widget())
|
||||
|
||||
# group by
|
||||
choices = [(dimension.name, dimension.label) for dimension in cube.dimensions
|
||||
if dimension.type not in ('datetime', 'date')]
|
||||
self.fields['drilldown'] = forms.MultipleChoiceField(
|
||||
label=_('Group by'), choices=choices, required=False,
|
||||
widget=build_select2_widget())
|
||||
self.base_fields['drilldown_x'] = forms.ChoiceField(
|
||||
label=_('Group by horizontaly'),
|
||||
choices=dimension_choices,
|
||||
required=False)
|
||||
|
||||
self.base_fields['drilldown_y'] = forms.ChoiceField(
|
||||
label=_('Group by vertically'),
|
||||
choices=dimension_choices,
|
||||
required=False)
|
||||
|
||||
# measures
|
||||
choices = [(measure.name, measure.label) for measure in cube.measures]
|
||||
self.fields['measures'] = forms.MultipleChoiceField(
|
||||
label=_('Measures'), choices=choices,
|
||||
widget=build_select2_widget())
|
||||
self.base_fields['measure'] = forms.ChoiceField(
|
||||
label=_('Measure'), choices=choices)
|
||||
|
||||
super(CubeForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(CubeForm, self).clean()
|
||||
|
||||
loop = cleaned_data.get('loop')
|
||||
drilldown_x = cleaned_data.get('drilldown_x')
|
||||
drilldown_y = cleaned_data.get('drilldown_y')
|
||||
|
||||
if loop and (loop == drilldown_x or loop == drilldown_y):
|
||||
raise ValidationError({'loop': _('You cannot use the same dimension for looping and'
|
||||
' grouping')})
|
||||
return cleaned_data
|
||||
|
|
|
@ -14,11 +14,15 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import json
|
||||
import hashlib
|
||||
import datetime
|
||||
import decimal
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
from django.http import Http404
|
||||
|
@ -29,24 +33,43 @@ from .ods import Workbook
|
|||
|
||||
|
||||
class Visualization(object):
|
||||
def __init__(self, cube, representation, measures, drilldown=None, filters=None):
|
||||
def __init__(self, cube, representation, measure, drilldown_x=None, drilldown_y=None,
|
||||
filters=None, loop=None):
|
||||
self.cube = cube
|
||||
self.representation = representation
|
||||
|
||||
self.measures = measures
|
||||
self.drilldown = drilldown or []
|
||||
self.measure = measure
|
||||
self.drilldown_x = drilldown_x
|
||||
self.drilldown_y = drilldown_y
|
||||
self.filters = filters or {}
|
||||
self.loop = loop
|
||||
|
||||
@property
|
||||
def drilldown(self):
|
||||
drilldown = []
|
||||
if self.drilldown_x:
|
||||
drilldown.append(self.drilldown_x)
|
||||
if self.drilldown_y:
|
||||
drilldown.append(self.drilldown_y)
|
||||
return drilldown
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'warehouse': self.cube.engine.warehouse.name,
|
||||
'cube': self.cube.name,
|
||||
'representation': self.representation,
|
||||
'measures': [measure.name for measure in self.measures],
|
||||
'drilldown': [drilldown.name for drilldown in self.drilldown],
|
||||
'measure': self.measure and self.measure.name,
|
||||
'drilldown_x': self.drilldown_x and self.drilldown_x.name,
|
||||
'drilldown_y': self.drilldown_y and self.drilldown_y.name,
|
||||
'filters': self.filters,
|
||||
'loop': self.loop,
|
||||
}
|
||||
|
||||
def copy(self):
|
||||
return Visualization(self.cube, self.representation, measure=self.measure,
|
||||
drilldown_x=self.drilldown_x, drilldown_y=self.drilldown_y,
|
||||
filters=copy.deepcopy(self.filters), loop=self.loop)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, d, request=None):
|
||||
for warehouse in get_warehouses(request=request):
|
||||
|
@ -63,7 +86,10 @@ class Visualization(object):
|
|||
measures = [cube.measures[name] for name in d['measures']]
|
||||
drilldown = [cube.dimensions[name] for name in d.get('drilldown', [])]
|
||||
filters = d.get('filters', {})
|
||||
return cls(cube, representation, measures, drilldown=drilldown, filters=filters)
|
||||
loop = d.get('loop')
|
||||
if loop:
|
||||
loop = cube.dimensions[loop]
|
||||
return cls(cube, representation, measures, drilldown=drilldown, filters=filters, loop=loop)
|
||||
|
||||
@classmethod
|
||||
def from_form(cls, cube, form):
|
||||
|
@ -73,23 +99,32 @@ class Visualization(object):
|
|||
if values and kw.startswith('filter__'):
|
||||
dimension_name = kw[8:]
|
||||
filters[dimension_name] = values
|
||||
measures = cleaned_data.get('measures', [])
|
||||
measures = [cube.measures[name] for name in measures]
|
||||
drilldown = cleaned_data.get('drilldown', [])
|
||||
drilldown = [cube.dimensions[name] for name in drilldown]
|
||||
return cls(cube, cleaned_data['representation'], measures, drilldown=drilldown,
|
||||
filters=filters)
|
||||
measure = cleaned_data.get('measure', [])
|
||||
measure = measure and cube.measures[measure]
|
||||
drilldown_x = cleaned_data.get('drilldown_x')
|
||||
drilldown_x = drilldown_x and cube.dimensions[drilldown_x]
|
||||
drilldown_y = cleaned_data.get('drilldown_y')
|
||||
drilldown_y = drilldown_y and cube.dimensions[drilldown_y]
|
||||
loop = cleaned_data.get('loop')
|
||||
loop = loop and cube.dimensions[loop]
|
||||
return cls(cube, cleaned_data['representation'],
|
||||
measure,
|
||||
drilldown_x=drilldown_x,
|
||||
drilldown_y=drilldown_y,
|
||||
filters=filters, loop=loop)
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
l = [self.cube.engine.warehouse.name, self.cube.name]
|
||||
if self.loop:
|
||||
l.append(self.loop.name)
|
||||
for kw, values in self.filters.iteritems():
|
||||
if values:
|
||||
if isinstance(values, dict):
|
||||
values = values.items()
|
||||
l.append('$'.join([kw] + sorted(map(unicode, values))))
|
||||
l += [dim.name for dim in self.drilldown]
|
||||
l += [measure.name for measure in self.measures]
|
||||
l += [self.measure.name]
|
||||
key = '$'.join(v.encode('utf8') for v in l)
|
||||
return hashlib.md5(key).hexdigest()
|
||||
|
||||
|
@ -118,7 +153,7 @@ class Visualization(object):
|
|||
def data(self):
|
||||
return self.cube.query(self.filters.items(),
|
||||
[dim.name for dim in self.drilldown],
|
||||
[measure.name for measure in self.measures])
|
||||
[self.measure.name])
|
||||
|
||||
def cached(self):
|
||||
key = self.key
|
||||
|
@ -128,6 +163,56 @@ class Visualization(object):
|
|||
cache.set(key, data)
|
||||
return data
|
||||
|
||||
def table(self):
|
||||
table = []
|
||||
if len(self.drilldown) == 2:
|
||||
x_labels = [x.label for x in self.drilldown_x.members]
|
||||
y_labels = [y.label for y in self.drilldown_y.members]
|
||||
used_x_label = set()
|
||||
used_y_label = set()
|
||||
|
||||
grid = {(x, y): None for x in x_labels for y in y_labels}
|
||||
|
||||
for row in self.stringified():
|
||||
x_label = unicode(row[0]['value'])
|
||||
y_label = unicode(row[1]['value'])
|
||||
used_x_label.add(x_label)
|
||||
used_y_label.add(y_label)
|
||||
grid[(x_label, y_label)] = row[2]['value']
|
||||
|
||||
table.append([''] + [x for x in x_labels if x in used_x_label])
|
||||
for y in y_labels:
|
||||
if y not in used_y_label:
|
||||
continue
|
||||
table.append([y] + [grid[(x, y)] for x in x_labels if x in used_x_label])
|
||||
if self.measure.expression.lower().startswith('count('):
|
||||
# ajout des totaux horizontaux
|
||||
table[0].append(_('Total'))
|
||||
for row in table[1:]:
|
||||
row.append(sum(v or 0 for v in row[1:]))
|
||||
table.append([_('Total')])
|
||||
for i in range(1, len(table[0])):
|
||||
table[-1].append(sum([
|
||||
row[i] or 0 for row in table[1:-1]]))
|
||||
return table
|
||||
elif self.drilldown_x:
|
||||
table.append([self.drilldown_x.label])
|
||||
table.append([self.measure.label])
|
||||
for row in self.stringified():
|
||||
table[0].append(row[0]['value'])
|
||||
table[1].append(row[1]['value'])
|
||||
elif self.drilldown_y:
|
||||
table.append([self.drilldown_y.label, self.measure.label])
|
||||
for row in self.stringified():
|
||||
table.append([
|
||||
row[0]['value'],
|
||||
row[1]['value']
|
||||
])
|
||||
else:
|
||||
value = self.stringified()[0][0]['value']
|
||||
table.append([self.measure.label, value])
|
||||
return table
|
||||
|
||||
def grouped_data(self):
|
||||
data = self.stringified()
|
||||
dims = len(self.drilldown)
|
||||
|
@ -156,6 +241,14 @@ class Visualization(object):
|
|||
prefix = []
|
||||
return helper(grouped)
|
||||
|
||||
def javascript(self):
|
||||
l = []
|
||||
l.append('var measure = %s;' % json.dumps(self.measure.to_json()))
|
||||
l.append('var loop = %s;' % json.dumps(self.loop.to_json() if self.loop else None))
|
||||
l.append('var drilldown = %s;' % json.dumps([dim.to_json() for dim in self.drilldown]))
|
||||
l.append('var data = %s;' % json.dumps(self.json_data()))
|
||||
return mark_safe('\n'.join(l))
|
||||
|
||||
def json_data(self):
|
||||
json_data = []
|
||||
for row in self.data():
|
||||
|
@ -174,18 +267,44 @@ class Visualization(object):
|
|||
|
||||
def ods(self):
|
||||
workbook = Workbook()
|
||||
sheet = workbook.add_sheet(self.cube.label)
|
||||
for j, m in enumerate(self.drilldown + self.measures):
|
||||
sheet.write(0, j, m.label)
|
||||
for i, row in enumerate(self.stringified()):
|
||||
for j, cell in enumerate(row):
|
||||
sheet.write(i + 1, j, unicode(cell['value']))
|
||||
|
||||
full_title = self.title()
|
||||
|
||||
for table in self:
|
||||
sheet_name = re.sub('[^a-zA-Z ]', '', table.table_title)
|
||||
sheet = workbook.add_sheet(sheet_name)
|
||||
|
||||
sheet.write(0, 0, full_title)
|
||||
for j, row in enumerate(table.table()):
|
||||
for i, value in enumerate(row):
|
||||
sheet.write(j + 1, i, unicode(0 if value is None else value))
|
||||
return workbook
|
||||
|
||||
def title(self):
|
||||
if self.drilldown:
|
||||
return _('{0} by {1}').format(human_join([measure.label for measure in self.measures]),
|
||||
human_join([dimension.label for dimension in
|
||||
self.drilldown]))
|
||||
l = []
|
||||
if self.measure:
|
||||
l.append(self.measure.label)
|
||||
if self.drilldown_x:
|
||||
l.append(self.drilldown_x.label)
|
||||
if self.drilldown_y:
|
||||
l.append(self.drilldown_y.label)
|
||||
if self.loop:
|
||||
l.append(self.loop.label)
|
||||
return u', '.join(l)
|
||||
|
||||
def __iter__(self):
|
||||
if self.loop:
|
||||
members = list(self.loop.members)
|
||||
d = list(self.cube.query(self.filters.items(), [self.loop.name],
|
||||
[self.measure.name]))
|
||||
names = [unicode(x[0]['value']) for x in d]
|
||||
members = [m for m in members if unicode(m.label) in names]
|
||||
for member in members:
|
||||
table = self.copy()
|
||||
table.loop = None
|
||||
table.filters[self.loop.name] = [member.id]
|
||||
table.table_title = unicode(member.label)
|
||||
yield table
|
||||
else:
|
||||
return human_join([measure.label for measure in self.measures])
|
||||
self.table_title = self.title()
|
||||
yield self
|
||||
|
|
|
@ -54,14 +54,7 @@ class CubeDisplayMixin(object):
|
|||
ctx = super(CubeDisplayMixin, self).get_context_data(**kwargs)
|
||||
ctx['warehouse'] = self.warehouse
|
||||
ctx['cube'] = self.cube
|
||||
|
||||
if self.visualization:
|
||||
ctx['visualization'] = self.visualization
|
||||
ctx['measures_json'] = json.dumps(
|
||||
[measure.to_json() for measure in self.visualization.measures])
|
||||
ctx['drilldown_json'] = json.dumps(
|
||||
[dim.to_json() for dim in self.visualization.drilldown])
|
||||
ctx['json'] = json.dumps(self.visualization.json_data(), indent=2)
|
||||
ctx['visualization'] = self.visualization
|
||||
return ctx
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue