0002-dataviz-reload-chart-filters-cell-to-reflect-subfilt.patch
combo/apps/dataviz/forms.py | ||
---|---|---|
19 | 19 | |
20 | 20 |
from django import forms |
21 | 21 |
from django.conf import settings |
22 |
from django.core.cache import cache |
|
22 | 23 |
from django.db import transaction |
23 | 24 |
from django.db.models import Q |
24 | 25 |
from django.db.models.fields import BLANK_CHOICE_DASH |
... | ... | |
278 | 279 | |
279 | 280 |
def __init__(self, *args, **kwargs): |
280 | 281 |
page = kwargs.pop('page') |
282 |
filters_cell_id = kwargs.pop('filters_cell_id', None) |
|
281 | 283 |
super().__init__(*args, **kwargs) |
282 | 284 | |
283 | 285 |
chart_cells = list(ChartNgCell.objects.filter(page=page, statistic__isnull=False).order_by('order')) |
... | ... | |
285 | 287 |
self.fields.clear() |
286 | 288 |
return |
287 | 289 | |
290 |
if filters_cell_id: |
|
291 |
for cell in chart_cells: |
|
292 |
cell.subfilters = cache.get(cell.get_cache_key(filters_cell_id), cell.subfilters) |
|
293 | ||
288 | 294 |
first_cell = chart_cells[0] |
289 | 295 |
for field in self._meta.fields: |
290 | 296 |
self.fields[field].initial = getattr(first_cell, field) |
combo/apps/dataviz/models.py | ||
---|---|---|
734 | 734 |
self.filter_params = {k: v for k, v in self.filter_params.items() if k in subfilter_ids} |
735 | 735 |
self.save() |
736 | 736 | |
737 |
def get_cache_key(self, filters_cell_id): |
|
738 |
return 'dataviz:%s:%s' % (filters_cell_id, self.pk) |
|
739 | ||
737 | 740 | |
738 | 741 |
@register_cell_class |
739 | 742 |
class ChartFiltersCell(CellBase): |
... | ... | |
752 | 755 |
from .forms import ChartFiltersForm |
753 | 756 | |
754 | 757 |
ctx = super().get_cell_extra_context(context) |
755 |
ctx['form'] = ChartFiltersForm(page=self.page) |
|
758 |
if 'filters_cell_id' in context['request'].GET: # detect refresh on submit |
|
759 |
ctx['form'] = ChartFiltersForm( |
|
760 |
data=context['request'].GET, |
|
761 |
page=self.page, |
|
762 |
filters_cell_id=context['request'].GET['filters_cell_id'], |
|
763 |
) |
|
764 |
else: |
|
765 |
ctx['form'] = ChartFiltersForm(page=self.page) |
|
766 | ||
756 | 767 |
return ctx |
combo/apps/dataviz/static/js/chartngcell.js | ||
---|---|---|
1 | 1 |
function get_graph_querystring(extra_context, width=undefined) { |
2 | 2 |
qs = []; |
3 |
if($('#chart-filters')) |
|
3 |
if($('#chart-filters')) {
|
|
4 | 4 |
qs.push($('#chart-filters').serialize()); |
5 |
qs.push('filters_cell_id=' + $('body').data('filters-cell-id')); |
|
6 |
} |
|
5 | 7 |
if(extra_context) |
6 | 8 |
qs.push('ctx=' + extra_context); |
7 | 9 |
if (window.location.search) |
combo/apps/dataviz/templates/combo/chart-filters.html | ||
---|---|---|
23 | 23 | |
24 | 24 |
<script> |
25 | 25 |
$(function () { |
26 |
if (!$('body').data('filters-cell-id')) { |
|
27 |
$('body').data('filters-cell-id', Math.random().toString(36).slice(2, 7)); |
|
28 | ||
29 |
var loaded_cell_count = 0; |
|
30 |
document.querySelectorAll('div.chartngcell embed').forEach(graph => { |
|
31 |
graph.addEventListener('load', function() { |
|
32 |
if (++loaded_cell_count == $('div.chartngcell embed').length) { |
|
33 |
combo_load_cell($('.chart-filters-cell')); |
|
34 |
loaded_cell_count = 0; |
|
35 |
} |
|
36 |
}); |
|
37 |
}); |
|
38 |
} |
|
39 | ||
26 | 40 |
start_field = $('#id_time_range_start'); |
27 | 41 |
end_field = $('#id_time_range_end'); |
28 | 42 |
$('#id_time_range').change(function() { |
... | ... | |
37 | 51 |
$('#chart-filters').submit(function(e) { |
38 | 52 |
e.preventDefault(); |
39 | 53 |
$(window).trigger('combo:refresh-graphs'); |
54 |
chart_cell = $(this).parents('.cell'); |
|
55 |
new_url = chart_cell.data('ajax-cell-url') + '?filters_cell_id=' + $('body').data('filters-cell-id') + '&' + $(this).serialize(); |
|
56 |
chart_cell.data('ajax-cell-url', new_url); |
|
40 | 57 |
}); |
41 | 58 |
}); |
42 | 59 |
</script> |
combo/apps/dataviz/views.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
from django.core import signing |
18 |
from django.core.cache import cache |
|
18 | 19 |
from django.core.exceptions import PermissionDenied |
19 | 20 |
from django.http import Http404, HttpResponse, HttpResponseBadRequest |
20 | 21 |
from django.shortcuts import render |
... | ... | |
24 | 25 |
from django.views.generic import DetailView |
25 | 26 |
from requests.exceptions import HTTPError |
26 | 27 | |
27 |
from combo.utils import get_templated_url, requests |
|
28 |
from combo.utils import NothingInCacheException, get_templated_url, requests
|
|
28 | 29 | |
29 | 30 |
from .forms import ChartNgPartialForm |
30 | 31 |
from .models import ChartNgCell, Gauge, MissingVariable, UnsupportedDataSet |
... | ... | |
42 | 43 | |
43 | 44 |
def dispatch(self, request, *args, **kwargs): |
44 | 45 |
self.cell = self.get_object() |
46 |
self.filters_cell_id = request.GET.get('filters_cell_id') |
|
45 | 47 | |
46 | 48 |
if not self.cell.page.is_visible(request.user): |
47 | 49 |
raise PermissionDenied() |
... | ... | |
50 | 52 |
if not self.cell.statistic or not self.cell.statistic.url: |
51 | 53 |
raise Http404('misconfigured cell') |
52 | 54 | |
55 |
if self.filters_cell_id: |
|
56 |
self.cell.subfilters = cache.get( |
|
57 |
self.cell.get_cache_key(self.filters_cell_id), self.cell.subfilters |
|
58 |
) |
|
53 | 59 |
return super().dispatch(request, *args, **kwargs) |
54 | 60 | |
55 | 61 |
def get(self, request, *args, **kwargs): |
... | ... | |
83 | 89 |
else: |
84 | 90 |
return self.error(_('Unknown HTTP error: %s' % e)) |
85 | 91 | |
92 |
if self.filters_cell_id: |
|
93 |
self.update_subfilters_cache(form.instance) |
|
94 | ||
86 | 95 |
if self.cell.chart_type == 'table': |
87 | 96 |
if not chart.raw_series: |
88 | 97 |
return self.error(_('No data')) |
... | ... | |
106 | 115 |
} |
107 | 116 |
return render(self.request, 'combo/dataviz-error.svg', context=context, content_type='image/svg+xml') |
108 | 117 | |
118 |
def update_subfilters_cache(self, cell): |
|
119 |
try: |
|
120 |
data = cell.get_statistic_data(raise_if_not_cached=True) |
|
121 |
except NothingInCacheException: |
|
122 |
pass # should not happen |
|
123 |
else: |
|
124 |
cache.set( |
|
125 |
cell.get_cache_key(self.filters_cell_id), data.json()['data'].get('subfilters', []), 300 |
|
126 |
) |
|
127 | ||
109 | 128 | |
110 | 129 |
dataviz_graph = xframe_options_sameorigin(DatavizGraphView.as_view()) |
combo/public/static/js/combo.public.js | ||
---|---|---|
4 | 4 |
var extra_context = $elem.data('extra-context'); |
5 | 5 |
$.support.cors = true; /* IE9 */ |
6 | 6 |
var qs; |
7 |
if (window.location.search) { |
|
7 |
if (url.includes('?')) { |
|
8 |
qs = '&'; |
|
9 |
if (window.location.search) { |
|
10 |
qs += window.location.search.slice(1) + '&'; |
|
11 |
} |
|
12 |
} else if (window.location.search) { |
|
8 | 13 |
qs = window.location.search + '&'; |
9 | 14 |
} else { |
10 | 15 |
qs = '?'; |
tests/test_dataviz.py | ||
---|---|---|
2617 | 2617 |
resp.form[field_prefix + 'form'] = 'food-request' |
2618 | 2618 |
manager_submit_cell(resp.form) |
2619 | 2619 |
assert field_prefix + 'menu' in resp.form.fields |
2620 | ||
2621 | ||
2622 |
@with_httmock(new_api_mock) |
|
2623 |
def test_chart_filters_cell_dynamic_subfilters(new_api_statistics, app, admin_user): |
|
2624 |
page = Page.objects.create(title='One', slug='index') |
|
2625 |
ChartFiltersCell.objects.create(page=page, order=1, placeholder='content') |
|
2626 |
cell = ChartNgCell.objects.create(page=page, order=2, placeholder='content') |
|
2627 |
cell.statistic = Statistic.objects.get(slug='with-subfilter') |
|
2628 |
cell.save() |
|
2629 | ||
2630 |
app = login(app) |
|
2631 |
resp = app.get('/') |
|
2632 |
assert 'form' in resp.form.fields |
|
2633 |
assert 'menu' not in resp.form.fields |
|
2634 | ||
2635 |
# ensure choice exist |
|
2636 |
resp.form['form'] = 'food-request' |
|
2637 | ||
2638 |
# simulate chart cell ajax refresh on form submission |
|
2639 |
app.get('/api/dataviz/graph/%s/' % cell.pk + '?form=food-request&filters_cell_id=xxx') |
|
2640 | ||
2641 |
# simulate filters cell ajax refresh after cell refresh |
|
2642 |
location = resp.pyquery('.chartfilterscell').attr('data-ajax-cell-url') |
|
2643 |
resp = app.get(location + '?form=food-request&filters_cell_id=xxx') |
|
2644 |
assert resp.form['form'].value == 'food-request' |
|
2645 |
assert 'menu' in resp.form.fields |
|
2646 | ||
2647 |
# check isolation between pages by modifying filters_cell_id |
|
2648 |
app.get('/api/dataviz/graph/%s/' % cell.pk + '?form=contact&filters_cell_id=yyy') |
|
2649 |
resp = app.get(location + '?form=food-request&filters_cell_id=xxx') |
|
2650 |
assert 'form' in resp.form.fields |
|
2651 |
assert 'menu' in resp.form.fields |
|
2620 |
- |