From a048744d539468d74440b2226953a51363726de9 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 15 Jan 2019 13:57:03 +0100 Subject: [PATCH] add support for string columns (fixes #29768) --- bijoe/schemas.py | 12 ++++++--- bijoe/templates/bijoe/cube_table.html | 2 +- bijoe/visualization/forms.py | 20 ++++++++++++-- bijoe/visualization/utils.py | 17 +++++++++++- tests/fixtures/schema1/01_schema.json | 6 +++++ tests/fixtures/schema1/01_schema.sql | 39 ++++++++++++++------------- tests/test_schema1.py | 15 +++++++++++ tox.ini | 1 + 8 files changed, 86 insertions(+), 26 deletions(-) diff --git a/bijoe/schemas.py b/bijoe/schemas.py index f1f00cb..bc0b53a 100644 --- a/bijoe/schemas.py +++ b/bijoe/schemas.py @@ -254,14 +254,20 @@ class Dimension(Base): filter_values = [filter_values] if not filter_values: return '', [] + is_none = None in filter_values + filter_values = [v for v in filter_values if v is not None] if self.type == 'integer': - values = map(int, filter_values) + values = [int(v) for v in filter_values] else: values = filter_values s = ', '.join(['%s'] * len(values)) if self.filter_expression: - return self.filter_expression % s, values - return '%s IN (%s)' % (value, s), values + expression = self.filter_expression % s + else: + expression = '%s IN (%s)' % (value, s) + if is_none: + expression = '((%s) OR (%s IS NULL))' % (expression, value) + return expression, values def join_kind(kind): diff --git a/bijoe/templates/bijoe/cube_table.html b/bijoe/templates/bijoe/cube_table.html index d75728c..4f24dc4 100644 --- a/bijoe/templates/bijoe/cube_table.html +++ b/bijoe/templates/bijoe/cube_table.html @@ -13,7 +13,7 @@ {% for value in row %} {% comment %}Only django 1.10 allow is None/True/False{% endcomment %} - {% if value|stringformat:"r" == "None" %}0{% elif value|stringformat:"r" == "True" %}{% trans "Oui" %}{% elif value|stringformat:"r" == "False" %}{% trans "Non" %}{% else %}{{ value }}{% endif %} + {{ value }} {% endfor %} {% endfor %} diff --git a/bijoe/visualization/forms.py b/bijoe/visualization/forms.py index e46c89c..1d7c089 100644 --- a/bijoe/visualization/forms.py +++ b/bijoe/visualization/forms.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import six + from django import forms from django.core.exceptions import ValidationError from django.utils.translation import ugettext as _ @@ -183,9 +185,23 @@ class CubeForm(forms.Form): self.base_fields[field_name] = NullBooleanField( label=dimension.label.capitalize(), required=False) else: - self.base_fields[field_name] = forms.MultipleChoiceField( + members = [] + for _id, label in dimension.members: + members.append((_id, six.text_type(_id), label)) + members.append((None, '__none__', _('None'))) + + def coercion_function(members): + def f(v): + for value, s, label in members: + if v == s: + return value + return None + return f + + self.base_fields[field_name] = forms.TypedMultipleChoiceField( label=dimension.label.capitalize(), - choices=dimension.members, + choices=[(s, label) for v, s, label in members], + coerce=coercion_function(members), required=False, widget=build_select2_multiple_widget()) diff --git a/bijoe/visualization/utils.py b/bijoe/visualization/utils.py index c01cd3a..7aa8141 100644 --- a/bijoe/visualization/utils.py +++ b/bijoe/visualization/utils.py @@ -159,6 +159,12 @@ class Visualization(object): if not s: s = 'moins d\'1 heure' value = s + elif value is not None and cell['type'] == 'bool': + value = _('Yes') if value else _('No') + elif value is None and cell['type'] in ('duration','integer'): + value = 0 + elif value is None and cell['type'] != 'integer': + value = _('None') cell['value'] = value return data @@ -179,12 +185,21 @@ class Visualization(object): def table(self): table = [] if len(self.drilldown) == 2: + if self.measure.type == 'integer': + default = 0 + elif self.measure.type == 'duration': + default = '0 s' + elif self.measure.type == 'percent': + default = '0 %' + else: + raise NotImplementedError(self.measure.type) + 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} + grid = {(x, y): default for x in x_labels for y in y_labels} for row in self.stringified(): x_label = unicode(row[0]['value']) diff --git a/tests/fixtures/schema1/01_schema.json b/tests/fixtures/schema1/01_schema.json index 823dfbe..64c33d3 100644 --- a/tests/fixtures/schema1/01_schema.json +++ b/tests/fixtures/schema1/01_schema.json @@ -157,6 +157,12 @@ "value": "outercategory.id", "order_by": "outercategory.ord", "value_label": "outercategory.label" + }, + { + "name": "string", + "label": "String", + "type": "string", + "value": "string" } ], "measures": [ diff --git a/tests/fixtures/schema1/01_schema.sql b/tests/fixtures/schema1/01_schema.sql index da77db4..ecf1242 100644 --- a/tests/fixtures/schema1/01_schema.sql +++ b/tests/fixtures/schema1/01_schema.sql @@ -25,7 +25,8 @@ CREATE TABLE facts ( innersubcategory_id integer references schema1.subcategory(id), leftsubcategory_id integer references schema1.subcategory(id), rightsubcategory_id integer references schema1.subcategory(id), - outersubcategory_id integer references schema1.subcategory(id) + outersubcategory_id integer references schema1.subcategory(id), + string varchar ); INSERT INTO category (ord, label) VALUES @@ -45,21 +46,21 @@ INSERT INTO subcategory (category_id, ord, label) VALUES (3, 0, 'subé9'); -INSERT INTO facts (date, datetime, integer, boolean, cnt, innersubcategory_id, leftsubcategory_id, rightsubcategory_id, outersubcategory_id) VALUES - ('2017-01-01', '2017-01-01 10:00', 1, FALSE, 10, 1, 1, 1, 1), - ('2017-01-02', '2017-01-02 10:00', 1, TRUE, 10, 3, 3, 3, 3), - ('2017-01-03', '2017-01-03 10:00', 1, FALSE, 10, NULL, NULL, NULL, NULL), - ('2017-01-04', '2017-01-04 10:00', 1, FALSE, 10, 1, 1, 1, 1), - ('2017-01-05', '2017-01-05 10:00', 1, TRUE, 10, 1, 1, 1, 1), - ('2017-01-06', '2017-01-06 10:00', 1, FALSE, 10, 1, 1, 1, 1), - ('2017-01-07', '2017-01-07 10:00', 1, TRUE, 10, 1, 1, 1, 1), - ('2017-01-08', '2017-01-08 10:00', 1, FALSE, 10, 1, 1, 1, 1), - ('2017-01-09', '2017-01-09 10:00', 1, TRUE, 10, 1, 1, 1, 1), - ('2017-01-10', '2017-01-10 10:00', 1, FALSE, 10, 1, 1, 1, 1), - ('2017-02-01', '2017-02-01 10:00', 1, TRUE, 10, 1, 1, 1, 1), - ('2017-03-01', '2017-03-01 10:00', 1, FALSE, 10, 1, 1, 1, 1), - ('2017-04-01', '2017-04-01 10:00', 1, TRUE, 10, 1, 1, 1, 1), - ('2017-05-01', '2017-05-01 10:00', 1, FALSE, 10, 1, 1, 1, 1), - ('2017-06-01', '2017-06-01 10:00', 1, TRUE, 10, 1, 1, 1, 1), - ('2017-07-01', '2017-07-01 10:00', 1, FALSE, 10, 1, 1, 1, 1), - ('2017-08-01', '2017-08-01 10:00', 1, TRUE, 10, 1, 1, 1, 1); +INSERT INTO facts (date, datetime, integer, boolean, cnt, innersubcategory_id, leftsubcategory_id, rightsubcategory_id, outersubcategory_id, string) VALUES + ('2017-01-01', '2017-01-01 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'), + ('2017-01-02', '2017-01-02 10:00', 1, TRUE, 10, 3, 3, 3, 3, 'b'), + ('2017-01-03', '2017-01-03 10:00', 1, FALSE, 10, NULL, NULL, NULL, NULL, 'a'), + ('2017-01-04', '2017-01-04 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'), + ('2017-01-05', '2017-01-05 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'c'), + ('2017-01-06', '2017-01-06 10:00', 1, FALSE, 10, 1, 1, 1, 1, NULL), + ('2017-01-07', '2017-01-07 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'a'), + ('2017-01-08', '2017-01-08 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'), + ('2017-01-09', '2017-01-09 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'a'), + ('2017-01-10', '2017-01-10 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'), + ('2017-02-01', '2017-02-01 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'a'), + ('2017-03-01', '2017-03-01 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'c'), + ('2017-04-01', '2017-04-01 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'a'), + ('2017-05-01', '2017-05-01 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'), + ('2017-06-01', '2017-06-01 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'c'), + ('2017-07-01', '2017-07-01 10:00', 1, FALSE, 10, 1, 1, 1, 1, 'a'), + ('2017-08-01', '2017-08-01 10:00', 1, TRUE, 10, 1, 1, 1, 1, 'b'); diff --git a/tests/test_schema1.py b/tests/test_schema1.py index 9d79d72..a7de3da 100644 --- a/tests/test_schema1.py +++ b/tests/test_schema1.py @@ -68,3 +68,18 @@ def test_boolean_dimension(schema1, app, admin): form.set('filter__boolean', [o[0] for o in form.fields['filter__boolean'][0].options if o[2] == 'Oui'][0]) response = form.submit('visualize') assert get_table(response) == [['Boolean', 'Oui'], ['number of rows', '8']] + + +def test_string_dimension(schema1, app, admin): + login(app, admin) + response = app.get('/').follow() + response = response.click('Facts 1') + form = response.form + form.set('representation', 'table') + form.set('measure', 'simple_count') + form.set('drilldown_x', 'string') + response = form.submit('visualize') + assert get_table(response) == [['String', 'a', 'b', 'c', 'Aucun(e)'], ['number of rows', '11', '2', '3', '1']] + form.set('filter__string', ['a', 'b', '__none__']) + response = form.submit('visualize') + assert get_table(response) == [['String', 'a', 'b', 'Aucun(e)'], ['number of rows', '11', '2', '1']] diff --git a/tox.ini b/tox.ini index f7c2581..790b686 100644 --- a/tox.ini +++ b/tox.ini @@ -25,6 +25,7 @@ deps = pytest-cov pytest-django pytest-freezegun + pytest-random WebTest django-webtest<1.9.3 pyquery -- 2.20.1