diff --git a/combo/apps/dataviz/forms.py b/combo/apps/dataviz/forms.py index 50f86f59..1701de0e 100644 --- a/combo/apps/dataviz/forms.py +++ b/combo/apps/dataviz/forms.py @@ -19,6 +19,7 @@ from collections import OrderedDict from django import forms from django.conf import settings +from django.core.cache import cache from django.db import transaction from django.db.models import Q from django.db.models.fields import BLANK_CHOICE_DASH @@ -278,6 +279,7 @@ class ChartFiltersForm(ChartFiltersMixin, forms.ModelForm): def __init__(self, *args, **kwargs): page = kwargs.pop('page') + filters_cell_id = kwargs.pop('filters_cell_id', None) super().__init__(*args, **kwargs) chart_cells = list(ChartNgCell.objects.filter(page=page, statistic__isnull=False).order_by('order')) @@ -285,6 +287,10 @@ class ChartFiltersForm(ChartFiltersMixin, forms.ModelForm): self.fields.clear() return + if filters_cell_id: + for cell in chart_cells: + cell.subfilters = cache.get(cell.get_cache_key(filters_cell_id), cell.subfilters) + first_cell = chart_cells[0] for field in self._meta.fields: self.fields[field].initial = getattr(first_cell, field) diff --git a/combo/apps/dataviz/models.py b/combo/apps/dataviz/models.py index 05c53689..a4f36dcc 100644 --- a/combo/apps/dataviz/models.py +++ b/combo/apps/dataviz/models.py @@ -738,6 +738,9 @@ class ChartNgCell(CellBase): self.filter_params = {k: v for k, v in self.filter_params.items() if k in subfilter_ids} self.save() + def get_cache_key(self, filters_cell_id): + return 'dataviz:%s:%s' % (filters_cell_id, self.pk) + @register_cell_class class ChartFiltersCell(CellBase): @@ -756,5 +759,13 @@ class ChartFiltersCell(CellBase): from .forms import ChartFiltersForm ctx = super().get_cell_extra_context(context) - ctx['form'] = ChartFiltersForm(page=self.page) + if 'filters_cell_id' in context['request'].GET: # detect refresh on submit + ctx['form'] = ChartFiltersForm( + data=context['request'].GET, + page=self.page, + filters_cell_id=context['request'].GET['filters_cell_id'], + ) + else: + ctx['form'] = ChartFiltersForm(page=self.page) + return ctx diff --git a/combo/apps/dataviz/static/js/chartngcell.js b/combo/apps/dataviz/static/js/chartngcell.js index 73ae1c36..71cf3b4f 100644 --- a/combo/apps/dataviz/static/js/chartngcell.js +++ b/combo/apps/dataviz/static/js/chartngcell.js @@ -1,7 +1,9 @@ function get_graph_querystring(extra_context, width=undefined) { qs = []; - if ($('#chart-filters')) + if ($('#chart-filters')) { qs.push($('#chart-filters').serialize()); + qs.push('filters_cell_id=' + $('body').data('filters-cell-id')); + } if (extra_context) qs.push('ctx=' + extra_context); if (window.location.search) diff --git a/combo/apps/dataviz/templates/combo/chart-filters.html b/combo/apps/dataviz/templates/combo/chart-filters.html index 496dbb1b..3d47d893 100644 --- a/combo/apps/dataviz/templates/combo/chart-filters.html +++ b/combo/apps/dataviz/templates/combo/chart-filters.html @@ -23,6 +23,20 @@ diff --git a/combo/apps/dataviz/views.py b/combo/apps/dataviz/views.py index 74bc2bea..5e1c3a88 100644 --- a/combo/apps/dataviz/views.py +++ b/combo/apps/dataviz/views.py @@ -15,6 +15,7 @@ # along with this program. If not, see . from django.core import signing +from django.core.cache import cache from django.core.exceptions import PermissionDenied from django.http import Http404, HttpResponse, HttpResponseBadRequest from django.shortcuts import render @@ -24,7 +25,7 @@ from django.views.decorators.clickjacking import xframe_options_sameorigin from django.views.generic import DetailView from requests.exceptions import HTTPError -from combo.utils import get_templated_url, requests +from combo.utils import NothingInCacheException, get_templated_url, requests from .forms import ChartNgPartialForm from .models import ChartNgCell, Gauge, MissingVariable, UnsupportedDataSet @@ -42,6 +43,7 @@ class DatavizGraphView(DetailView): def dispatch(self, request, *args, **kwargs): self.cell = self.get_object() + self.filters_cell_id = request.GET.get('filters_cell_id') if not self.cell.page.is_visible(request.user): raise PermissionDenied() @@ -50,6 +52,10 @@ class DatavizGraphView(DetailView): if not self.cell.statistic or not self.cell.statistic.url: raise Http404('misconfigured cell') + if self.filters_cell_id: + self.cell.subfilters = cache.get( + self.cell.get_cache_key(self.filters_cell_id), self.cell.subfilters + ) return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): @@ -83,6 +89,9 @@ class DatavizGraphView(DetailView): else: return self.error(_('Unknown HTTP error: %s' % e)) + if self.filters_cell_id: + self.update_subfilters_cache(form.instance) + if self.cell.chart_type == 'table': if not chart.raw_series: return self.error(_('No data')) @@ -106,5 +115,15 @@ class DatavizGraphView(DetailView): } return render(self.request, 'combo/dataviz-error.svg', context=context, content_type='image/svg+xml') + def update_subfilters_cache(self, cell): + try: + data = cell.get_statistic_data(raise_if_not_cached=True) + except NothingInCacheException: + pass # should not happen + else: + cache.set( + cell.get_cache_key(self.filters_cell_id), data.json()['data'].get('subfilters', []), 300 + ) + dataviz_graph = xframe_options_sameorigin(DatavizGraphView.as_view()) diff --git a/combo/public/static/js/combo.public.js b/combo/public/static/js/combo.public.js index f41e9d61..7291ee1d 100644 --- a/combo/public/static/js/combo.public.js +++ b/combo/public/static/js/combo.public.js @@ -4,7 +4,12 @@ function combo_load_cell(elem) { var extra_context = $elem.data('extra-context'); $.support.cors = true; /* IE9 */ var qs; - if (window.location.search) { + if (url.includes('?')) { + qs = '&'; + if (window.location.search) { + qs += window.location.search.slice(1) + '&'; + } + } else if (window.location.search) { qs = window.location.search + '&'; } else { qs = '?'; diff --git a/tests/test_dataviz.py b/tests/test_dataviz.py index d358149e..292402c5 100644 --- a/tests/test_dataviz.py +++ b/tests/test_dataviz.py @@ -2655,3 +2655,35 @@ def test_chartng_cell_subfilter_page_variable(new_api_statistics, app, admin_use resp.form[field_prefix + 'form'] = 'contact' manager_submit_cell(resp.form) assert field_prefix + 'menu' in resp.form.fields + + +@with_httmock(new_api_mock) +def test_chart_filters_cell_dynamic_subfilters(new_api_statistics, app, admin_user): + page = Page.objects.create(title='One', slug='index') + ChartFiltersCell.objects.create(page=page, order=1, placeholder='content') + cell = ChartNgCell.objects.create(page=page, order=2, placeholder='content') + cell.statistic = Statistic.objects.get(slug='with-subfilter') + cell.save() + + app = login(app) + resp = app.get('/') + assert 'filter-form' in resp.form.fields + assert 'filter-menu' not in resp.form.fields + + # ensure choice exist + resp.form['filter-form'] = 'food-request' + + # simulate chart cell ajax refresh on form submission + app.get('/api/dataviz/graph/%s/' % cell.pk + '?filter-form=food-request&filters_cell_id=xxx') + + # simulate filters cell ajax refresh after cell refresh + location = resp.pyquery('.chartfilterscell').attr('data-ajax-cell-url') + resp = app.get(location + '?filter-form=food-request&filters_cell_id=xxx') + assert resp.form['filter-form'].value == 'food-request' + assert 'filter-menu' in resp.form.fields + + # check isolation between pages by modifying filters_cell_id + app.get('/api/dataviz/graph/%s/' % cell.pk + '?filter-form=contact&filters_cell_id=yyy') + resp = app.get(location + '?filter-form=food-request&filters_cell_id=xxx') + assert 'filter-form' in resp.form.fields + assert 'filter-menu' in resp.form.fields