0001-dataviz-allow-setting-time-range-using-template-5761.patch
combo/apps/dataviz/forms.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 datetime |
|
17 | 18 |
from collections import OrderedDict |
18 | 19 | |
19 | 20 |
from django import forms |
20 | 21 |
from django.conf import settings |
21 | 22 |
from django.db import transaction |
22 | 23 |
from django.db.models import Q |
24 |
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist |
|
23 | 25 |
from django.utils.translation import ugettext_lazy as _ |
24 | 26 | |
25 | 27 |
from combo.utils import cache_during_request, requests, spooler |
... | ... | |
69 | 71 |
'time_range', |
70 | 72 |
'time_range_start', |
71 | 73 |
'time_range_end', |
74 |
'time_range_start_template', |
|
75 |
'time_range_end_template', |
|
72 | 76 |
'chart_type', |
73 | 77 |
'height', |
74 | 78 |
'sort_order', |
... | ... | |
85 | 89 | |
86 | 90 |
field_ids = list(self._meta.fields) |
87 | 91 |
if not self.instance.statistic or self.instance.statistic.service_slug == 'bijoe': |
88 |
field_ids = [ |
|
89 |
x for x in field_ids if x not in ('time_range', 'time_range_start', 'time_range_end') |
|
90 |
] |
|
92 |
exclude = ( |
|
93 |
'time_range', |
|
94 |
'time_range_start', |
|
95 |
'time_range_end', |
|
96 |
'time_range_start_template', |
|
97 |
'time_range_end_template', |
|
98 |
) |
|
99 |
field_ids = [x for x in field_ids if x not in exclude] |
|
91 | 100 | |
92 | 101 |
stat_field = self.fields['statistic'] |
93 | 102 |
if not self.instance.statistic: |
... | ... | |
142 | 151 |
for choice in self.time_intervals: |
143 | 152 |
if choice[0].strip('_') not in choice_ids: |
144 | 153 |
self.fields['time_interval'].choices.append(choice) |
154 | ||
155 |
def clean(self): |
|
156 |
for template_field in ('time_range_start_template', 'time_range_end_template'): |
|
157 |
if not self.cleaned_data.get(template_field): |
|
158 |
continue |
|
159 |
context = {'now': datetime.datetime.now, 'today': datetime.datetime.now} |
|
160 |
try: |
|
161 |
date = Template('{{ %s|date:"Y-m-d" }}' % self.cleaned_data[template_field]).render( |
|
162 |
Context(context) |
|
163 |
) |
|
164 |
except (VariableDoesNotExist, TemplateSyntaxError) as e: |
|
165 |
self.add_error(template_field, e) |
|
166 |
else: |
|
167 |
if not date: |
|
168 |
self.add_error(template_field, _('Template does not evaluate to a valid date.')) |
combo/apps/dataviz/migrations/0019_auto_20211006_1525.py | ||
---|---|---|
1 |
# Generated by Django 2.2.19 on 2021-10-06 13:25 |
|
2 | ||
3 |
from django.db import migrations, models |
|
4 | ||
5 |
import combo.data.models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('dataviz', '0018_auto_20210723_1318'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='chartngcell', |
|
17 |
name='time_range_end_template', |
|
18 |
field=models.CharField( |
|
19 |
blank=True, |
|
20 |
max_length=200, |
|
21 |
validators=[combo.data.models.django_template_validator], |
|
22 |
verbose_name='To', |
|
23 |
), |
|
24 |
), |
|
25 |
migrations.AddField( |
|
26 |
model_name='chartngcell', |
|
27 |
name='time_range_start_template', |
|
28 |
field=models.CharField( |
|
29 |
blank=True, |
|
30 |
max_length=200, |
|
31 |
validators=[combo.data.models.django_template_validator], |
|
32 |
verbose_name='From', |
|
33 |
help_text='Template code returning a date. For example, Monday in two weeks would be today|add_days:"14"|adjust_to_week_monday.', |
|
34 |
), |
|
35 |
), |
|
36 |
] |
combo/apps/dataviz/models.py | ||
---|---|---|
25 | 25 |
from django.conf import settings |
26 | 26 |
from django.contrib.postgres.fields import JSONField |
27 | 27 |
from django.db import models, transaction |
28 |
from django.template import Context, Template |
|
28 | 29 |
from django.template.defaultfilters import date as format_date |
29 | 30 |
from django.urls import reverse |
30 | 31 |
from django.utils import timezone |
... | ... | |
36 | 37 |
from requests.exceptions import HTTPError, RequestException |
37 | 38 | |
38 | 39 |
from combo.data.library import register_cell_class |
39 |
from combo.data.models import CellBase |
|
40 |
from combo.data.models import CellBase, django_template_validator
|
|
40 | 41 |
from combo.utils import get_templated_url, requests, spooler |
41 | 42 | |
42 | 43 | |
... | ... | |
158 | 159 |
('previous-week', _('Previous week')), |
159 | 160 |
('current-week', _('Current week')), |
160 | 161 |
('next-week', _('Next week')), |
161 |
('range', _('Free range')), |
|
162 |
('range', _('Free range (date)')), |
|
163 |
('range-template', _('Free range (template)')), |
|
162 | 164 |
) |
163 | 165 | |
164 | 166 | |
... | ... | |
185 | 187 |
) |
186 | 188 |
time_range_start = models.DateField(_('From'), null=True, blank=True) |
187 | 189 |
time_range_end = models.DateField(_('To'), null=True, blank=True) |
190 |
time_range_start_template = models.CharField( |
|
191 |
_('From'), |
|
192 |
max_length=200, |
|
193 |
blank=True, |
|
194 |
validators=[django_template_validator], |
|
195 |
help_text=_( |
|
196 |
'Template code returning a date. For example, Monday in two weeks would be today|add_days:"14"|adjust_to_week_monday.' |
|
197 |
), |
|
198 |
) |
|
199 |
time_range_end_template = models.CharField( |
|
200 |
_('To'), |
|
201 |
max_length=200, |
|
202 |
blank=True, |
|
203 |
validators=[django_template_validator], |
|
204 |
) |
|
188 | 205 |
chart_type = models.CharField( |
189 | 206 |
_('Chart Type'), |
190 | 207 |
max_length=20, |
... | ... | |
394 | 411 |
params['start'] = self.time_range_start |
395 | 412 |
if self.time_range_end: |
396 | 413 |
params['end'] = self.time_range_end |
414 |
elif self.time_range == 'range-template': |
|
415 |
context = {'now': datetime.now, 'today': datetime.now} |
|
416 |
if self.time_range_start_template: |
|
417 |
params['start'] = Template('{{ %s|date:"Y-m-d" }}' % self.time_range_start_template).render( |
|
418 |
Context(context) |
|
419 |
) |
|
420 |
if self.time_range_end_template: |
|
421 |
params['end'] = Template('{{ %s|date:"Y-m-d" }}' % self.time_range_end_template).render( |
|
422 |
Context(context) |
|
423 |
) |
|
397 | 424 |
if 'time_interval' in params and params['time_interval'].startswith('_'): |
398 | 425 |
params['time_interval'] = 'day' |
399 | 426 |
return params |
combo/apps/dataviz/templates/combo/chartngcell_form.html | ||
---|---|---|
11 | 11 |
$(function () { |
12 | 12 |
start_field = $('#id_cdataviz_chartngcell-{{ cell.pk }}-time_range_start'); |
13 | 13 |
end_field = $('#id_cdataviz_chartngcell-{{ cell.pk }}-time_range_end'); |
14 |
start_field_template = $('#id_cdataviz_chartngcell-{{ cell.pk }}-time_range_start_template'); |
|
15 |
end_field_template = $('#id_cdataviz_chartngcell-{{ cell.pk }}-time_range_end_template'); |
|
14 | 16 |
$('#id_cdataviz_chartngcell-{{ cell.pk }}-time_range').change(function() { |
15 | 17 |
if(this.value == 'range') { |
16 | 18 |
start_field.parent().show(); |
... | ... | |
19 | 21 |
start_field.parent().hide(); |
20 | 22 |
end_field.parent().hide(); |
21 | 23 |
} |
24 |
if(this.value == 'range_template') { |
|
25 |
start_field_template.parent().show(); |
|
26 |
end_field_template.parent().show(); |
|
27 |
} else { |
|
28 |
start_field_template.parent().hide(); |
|
29 |
end_field_template.parent().hide(); |
|
30 |
} |
|
22 | 31 |
}).change(); |
23 | 32 |
}); |
24 | 33 |
</script> |
combo/context_processors.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 | ||
18 | 17 |
from django.conf import settings |
19 | 18 | |
20 | 19 |
from combo.apps.pwa.models import PwaSettings |
tests/test_dataviz.py | ||
---|---|---|
1098 | 1098 |
assert 'time_range' not in resp.form.fields |
1099 | 1099 |
assert 'time_range_start' not in resp.form.fields |
1100 | 1100 |
assert 'time_range_end' not in resp.form.fields |
1101 |
assert 'time_range_start_template' not in resp.form.fields |
|
1102 |
assert 'time_range_end_template_end' not in resp.form.fields |
|
1101 | 1103 | |
1102 | 1104 |
cell.statistic = Statistic.objects.get(slug='example') |
1103 | 1105 |
cell.save() |
... | ... | |
1111 | 1113 |
assert 'time_range' not in resp.form.fields |
1112 | 1114 |
assert 'time_range_start' not in resp.form.fields |
1113 | 1115 |
assert 'time_range_end' not in resp.form.fields |
1116 |
assert 'time_range_start_template' not in resp.form.fields |
|
1117 |
assert 'time_range_end_template_end' not in resp.form.fields |
|
1114 | 1118 | |
1115 | 1119 |
cell.statistic = Statistic.objects.get(slug='unavailable-stat') |
1116 | 1120 |
cell.save() |
... | ... | |
1200 | 1204 |
assert cell.time_range == '' |
1201 | 1205 | |
1202 | 1206 | |
1207 |
@with_httmock(new_api_mock) |
|
1208 |
@pytest.mark.freeze_time('2021-10-06') |
|
1209 |
def test_chartng_cell_manager_new_api_time_range_templates(app, admin_user, new_api_statistics): |
|
1210 |
page = Page.objects.create(title='One', slug='index') |
|
1211 |
cell = ChartNgCell(page=page, order=1, placeholder='content') |
|
1212 |
cell.statistic = Statistic.objects.get(slug='one-serie') |
|
1213 |
cell.save() |
|
1214 | ||
1215 |
app = login(app) |
|
1216 |
resp = app.get('/manage/pages/%s/' % page.id) |
|
1217 |
field_prefix = 'cdataviz_chartngcell-%s-' % cell.id |
|
1218 | ||
1219 |
resp.form[field_prefix + 'time_range'] = 'range-template' |
|
1220 |
resp.form[field_prefix + 'time_range_start_template'] = 'today|add_days:"7"|adjust_to_week_monday' |
|
1221 |
resp.form[field_prefix + 'time_range_end_template'] = 'now|add_days:"14"|adjust_to_week_monday' |
|
1222 |
resp = resp.form.submit().follow() |
|
1223 |
cell.refresh_from_db() |
|
1224 |
assert cell.time_range == 'range-template' |
|
1225 |
assert cell.time_range_start_template == 'today|add_days:"7"|adjust_to_week_monday' |
|
1226 |
assert cell.time_range_end_template == 'now|add_days:"14"|adjust_to_week_monday' |
|
1227 | ||
1228 |
resp.form[field_prefix + 'time_range_start_template'] = '' |
|
1229 |
resp.form[field_prefix + 'time_range_end_template'] = '' |
|
1230 |
resp = resp.form.submit().follow() |
|
1231 |
cell.refresh_from_db() |
|
1232 |
assert cell.time_range_start_template == '' |
|
1233 |
assert cell.time_range_end_template == '' |
|
1234 | ||
1235 |
resp.form[field_prefix + 'time_range_start_template'] = 'xxx' |
|
1236 |
resp = resp.form.submit() |
|
1237 |
assert 'Template does not evaluate to a valid date.' in resp.text |
|
1238 | ||
1239 |
resp = app.get('/manage/pages/%s/' % page.id) |
|
1240 |
resp.form[field_prefix + 'time_range_start_template'] = 'today|xxx' |
|
1241 |
resp = resp.form.submit() |
|
1242 |
assert 'Invalid filter' in resp.text |
|
1243 | ||
1244 |
resp = app.get('/manage/pages/%s/' % page.id) |
|
1245 |
resp.form[field_prefix + 'time_range_start_template'] = 'today|date:xxx' |
|
1246 |
resp = resp.form.submit() |
|
1247 |
assert 'Failed lookup for key [xxx]' in resp.text |
|
1248 | ||
1249 | ||
1203 | 1250 |
@with_httmock(new_api_mock) |
1204 | 1251 |
def test_chartng_cell_manager_new_api_dynamic_fields(app, admin_user, new_api_statistics): |
1205 | 1252 |
page = Page.objects.create(title='One', slug='index') |
... | ... | |
1435 | 1482 |
request = new_api_mock.call['requests'][-1] |
1436 | 1483 |
assert 'start=2020-10-01' in request.url and 'end=2020-11-03' in request.url |
1437 | 1484 | |
1485 |
cell.time_range = 'range-template' |
|
1486 |
cell.save() |
|
1487 |
cell.get_chart() |
|
1488 |
request = new_api_mock.call['requests'][-1] |
|
1489 |
assert 'start' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query) |
|
1490 |
assert 'end' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query) |
|
1491 | ||
1492 |
cell.time_range_start_template = 'today|add_days:"7"|adjust_to_week_monday' |
|
1493 |
cell.save() |
|
1494 |
cell.get_chart() |
|
1495 |
request = new_api_mock.call['requests'][-1] |
|
1496 |
assert 'start=2020-03-09' in request.url |
|
1497 |
assert 'end' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query) |
|
1498 | ||
1499 |
cell.time_range_end_template = 'today|add_days:"14"|adjust_to_week_monday' |
|
1500 |
cell.save() |
|
1501 |
cell.get_chart() |
|
1502 |
request = new_api_mock.call['requests'][-1] |
|
1503 |
assert 'start=2020-03-09' in request.url and 'end=2020-03-16' in request.url |
|
1504 | ||
1438 | 1505 | |
1439 | 1506 |
@with_httmock(new_api_mock) |
1440 | 1507 |
def test_chartng_cell_new_api_filter_params_month(new_api_statistics, nocache, freezer): |
1441 |
- |