Projet

Général

Profil

0001-dataviz-add-week-filters-55417.patch

Valentin Deniaud, 15 juillet 2021 15:52

Télécharger (14,7 ko)

Voir les différences:

Subject: [PATCH] dataviz: add week filters (#55417)

 combo/apps/dataviz/forms.py                   |  1 +
 .../migrations/0012_auto_20201126_1557.py     |  1 +
 .../migrations/0016_auto_20201215_1624.py     | 10 +--
 combo/apps/dataviz/models.py                  | 73 ++++++++++++-----
 tests/test_dataviz.py                         | 81 +++++++++++++++++--
 5 files changed, 135 insertions(+), 31 deletions(-)
combo/apps/dataviz/forms.py
56 56
class ChartNgForm(forms.ModelForm):
57 57
    blank_choice = ('', '---------')
58 58
    time_intervals = (
59
        ('_week', _('Week')),
59 60
        ('_month', _('Month')),
60 61
        ('_year', _('Year')),
61 62
        ('_weekday', _('Week day')),
combo/apps/dataviz/migrations/0012_auto_20201126_1557.py
46 46
                related_name='cells',
47 47
                to='dataviz.Statistic',
48 48
                verbose_name='Data',
49
                help_text='This list may take a few seconds to be updated, please refresh the page if an item is missing.',
49 50
            ),
50 51
        ),
51 52
    ]
combo/apps/dataviz/migrations/0016_auto_20201215_1624.py
4 4

  
5 5
from django.db import migrations, models
6 6

  
7
from combo.apps.dataviz.models import TIME_FILTERS
8

  
7 9

  
8 10
class Migration(migrations.Migration):
9 11

  
......
17 19
            name='time_range',
18 20
            field=models.CharField(
19 21
                blank=True,
20
                choices=[
21
                    ('current-year', 'Current year'),
22
                    ('previous-year', 'Previous year'),
23
                    ('current-month', 'Current month'),
24
                    ('previous-month', 'Previous month'),
25
                    ('range', 'Free range'),
26
                ],
22
                choices=TIME_FILTERS,
27 23
                max_length=20,
28 24
                verbose_name='Filtering (time)',
29 25
            ),
combo/apps/dataviz/models.py
22 22

  
23 23
import pygal
24 24
import pygal.util
25
from dateutil.relativedelta import relativedelta
25
from dateutil.relativedelta import MO, relativedelta
26 26
from django.conf import settings
27 27
from django.contrib.postgres.fields import JSONField
28 28
from django.db import models, transaction
......
149 149
        return (self.slug, self.site_slug, self.service_slug)
150 150

  
151 151

  
152
TIME_FILTERS = (
153
    ('previous-year', _('Previous year')),
154
    ('current-year', _('Current year')),
155
    ('next-year', _('Next year')),
156
    ('previous-month', _('Previous month')),
157
    ('current-month', _('Current month')),
158
    ('next-month', _('Next month')),
159
    ('previous-week', _('Previous week')),
160
    ('current-week', _('Current week')),
161
    ('next-week', _('Next week')),
162
    ('range', _('Free range')),
163
)
164

  
165

  
152 166
@register_cell_class
153 167
class ChartNgCell(CellBase):
154
    TIME_FILTERS = (
155
        ('current-year', _('Current year')),
156
        ('previous-year', _('Previous year')),
157
        ('current-month', _('Current month')),
158
        ('previous-month', _('Previous month')),
159
        ('range', _('Free range')),
160
    )
161

  
162 168
    statistic = models.ForeignKey(
163 169
        verbose_name=_('Data'),
164 170
        to=Statistic,
......
176 182
        _('Filtering (time)'),
177 183
        max_length=20,
178 184
        blank=True,
179
        choices=(
180
            ('current-year', _('Current year')),
181
            ('previous-year', _('Previous year')),
182
            ('current-month', _('Current month')),
183
            ('previous-month', _('Previous month')),
184
            ('range', _('Free range')),
185
        ),
185
        choices=TIME_FILTERS,
186 186
    )
187 187
    time_range_start = models.DateField(_('From'), null=True, blank=True)
188 188
    time_range_end = models.DateField(_('To'), null=True, blank=True)
......
368 368
        elif self.time_range == 'previous-year':
369 369
            params['start'] = date(year=now.year - 1, month=1, day=1)
370 370
            params['end'] = date(year=now.year, month=1, day=1)
371
        elif self.time_range == 'next-year':
372
            params['start'] = date(year=now.year + 1, month=1, day=1)
373
            params['end'] = date(year=now.year + 2, month=1, day=1)
371 374
        elif self.time_range == 'current-month':
372 375
            params['start'] = date(year=now.year, month=now.month, day=1)
373 376
        elif self.time_range == 'previous-month':
374 377
            params['start'] = date(year=now.year, month=now.month - 1, day=1)
375 378
            params['end'] = date(year=now.year, month=now.month, day=1)
379
        elif self.time_range == 'next-month':
380
            params['start'] = date(year=now.year, month=now.month + 1, day=1)
381
            params['end'] = date(year=now.year, month=now.month + 2, day=1)
382
        elif self.time_range == 'current-week':
383
            params['start'] = now + relativedelta(weekday=MO(-1))
384
            params['end'] = now + relativedelta(weekday=MO(+1), days=+1)
385
        elif self.time_range == 'previous-week':
386
            params['start'] = now + relativedelta(weekday=MO(-2))
387
            params['end'] = now + relativedelta(weekday=MO(-1))
388
        elif self.time_range == 'next-week':
389
            params['start'] = now + relativedelta(weekday=MO(+1), days=+1)
390
            params['end'] = now + relativedelta(weekday=MO(+2), days=+1)
376 391
        elif self.time_range == 'range':
377 392
            if self.time_range_start:
378 393
                params['start'] = self.time_range_start
......
571 586
        dates = [datetime.strptime(label, '%Y-%m-%d') for label in data['x_labels']]
572 587
        min_date, max_date = min(dates), max(dates)
573 588

  
589
        date_formats = {
590
            'day': 'd-m-Y',
591
            # Translators: This indicates week number followed by year, for example it can yield W2-2021.
592
            # First "W" is the first letter of the word "week" and should be translated accordingly, second
593
            # "W" and "o" are interpreted by Django's date filter and should be left as is. First W is
594
            # backslash escaped to prevent it from being interpreted, translators should refer to Django's
595
            # documentation in order to know if the new letter resulting of translation should be escaped or not.
596
            '_week': gettext('\WW-o'),
597
            '_month': 'm-Y',
598
            '_year': 'Y',
599
            '_weekday': 'l',
600
        }
574 601
        if interval == 'day':
575 602
            x_labels = [
576
                (min_date + timedelta(days=i)).strftime('%d-%m-%Y')
603
                format_date(min_date + timedelta(days=i), date_formats['day'])
577 604
                for i in range((max_date - min_date).days + 1)
578 605
            ]
579 606
        elif interval == '_month':
580 607
            month_difference = max_date.month - min_date.month + (max_date.year - min_date.year) * 12
581 608
            x_labels = [
582
                (min_date + relativedelta(months=i)).strftime('%m-%Y') for i in range(month_difference + 1)
609
                format_date(min_date + relativedelta(months=i), date_formats['_month'])
610
                for i in range(month_difference + 1)
583 611
            ]
584 612
        elif interval == '_year':
585 613
            x_labels = [str(year) for year in range(min_date.year, max_date.year + 1)]
586 614
        elif interval == '_weekday':
587 615
            x_labels = [str(label) for label in WEEKDAYS.values()]
616
        elif interval == '_week':
617
            x_labels = []
618
            date, last_date = min_date, max_date
619
            if min_date.weekday() > max_date.weekday():
620
                last_date += relativedelta(weeks=1)
621
            while date <= last_date:
622
                x_labels.append(format_date(date, date_formats['_week']))
623
                date += relativedelta(weeks=1)
588 624

  
589 625
        aggregates = OrderedDict((label, [0] * len(series_data)) for label in x_labels)
590
        date_formats = {'day': 'd-m-Y', '_month': 'm-Y', '_year': 'Y', '_weekday': 'l'}
591 626
        for i, date in enumerate(dates):
592 627
            key = format_date(date, date_formats[interval])
593 628
            for j in range(len(series_data)):
tests/test_dataviz.py
351 351
                }
352 352
            ],
353 353
        },
354
        {
355
            'url': 'https://authentic.example.com/api/statistics/leap-week/',
356
            'name': 'Same week spanning two years',
357
            'id': 'leap-week',
358
            "filters": [
359
                {
360
                    "default": "day",
361
                    "id": "time_interval",
362
                    "label": "Time interval",
363
                    "options": [
364
                        {"id": "day", "label": "Day"},
365
                    ],
366
                    "required": True,
367
                }
368
            ],
369
        },
354 370
    ]
355 371
}
356 372

  
......
396 412
            },
397 413
        }
398 414
        return {'content': json.dumps(response), 'request': request, 'status_code': 200}
415
    if url.path == '/api/statistics/leap-week/':
416
        response = {
417
            'data': {
418
                'series': [
419
                    {'data': [None, 1, 16, 2], 'label': 'Serie 1'},
420
                ],
421
                'x_labels': ['2020-12-30', '2020-12-31', '2021-01-01', '2021-01-02'],
422
            },
423
        }
424
        return {'content': json.dumps(response), 'request': request, 'status_code': 200}
399 425

  
400 426

  
401 427
@pytest.fixture
......
1120 1146
        ('day', False, 'Day'),
1121 1147
        ('month', True, 'Month'),
1122 1148
        ('year', False, 'Year'),
1149
        ('_week', False, 'Week'),
1123 1150
        ('_weekday', False, 'Week day'),
1124 1151
    ]
1125 1152

  
......
1346 1373

  
1347 1374

  
1348 1375
@with_httmock(new_api_mock)
1349
def test_chartng_cell_new_api_filter_params(new_api_statistics, nocache, freezer):
1376
@pytest.mark.parametrize('date', ['2020-03-02 12:01', '2020-03-05 12:01'])  # Monday and Thursday
1377
def test_chartng_cell_new_api_filter_params(new_api_statistics, nocache, freezer, date):
1350 1378
    page = Page.objects.create(title='One', slug='index')
1351 1379
    cell = ChartNgCell(page=page, order=1, placeholder='content')
1352 1380
    cell.statistic = Statistic.objects.get(slug='one-serie')
......
1364 1392
    assert 'time_interval=month' in request.url
1365 1393
    assert 'ou=default' in request.url
1366 1394

  
1367
    freezer.move_to('2020-03-02 12:01')
1395
    freezer.move_to(date)
1368 1396
    cell.time_range = 'previous-year'
1369 1397
    cell.save()
1370 1398
    chart = cell.get_chart()
......
1373 1401
    assert 'ou=default' in request.url
1374 1402
    assert 'start=2019-01-01' in request.url and 'end=2020-01-01' in request.url
1375 1403

  
1404
    cell.time_range = 'current-week'
1405
    cell.save()
1406
    chart = cell.get_chart()
1407
    request = new_api_mock.call['requests'][-1]
1408
    assert 'start=2020-03-02' in request.url and 'end=2020-03-09' in request.url
1409

  
1410
    cell.time_range = 'previous-week'
1411
    cell.save()
1412
    chart = cell.get_chart()
1413
    request = new_api_mock.call['requests'][-1]
1414
    assert 'start=2020-02-24' in request.url and 'end=2020-03-02' in request.url
1415

  
1416
    cell.time_range = 'next-week'
1417
    cell.save()
1418
    chart = cell.get_chart()
1419
    request = new_api_mock.call['requests'][-1]
1420
    assert 'start=2020-03-09' in request.url and 'end=2020-03-16' in request.url
1421

  
1376 1422
    cell.time_range = 'range'
1377 1423
    cell.save()
1378 1424
    chart = cell.get_chart()
1379
    request = new_api_mock.call['requests'][3]
1425
    request = new_api_mock.call['requests'][-1]
1380 1426
    assert 'start' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query)
1381 1427
    assert 'end' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query)
1382 1428

  
1383 1429
    cell.time_range_start = '2020-10-01'
1384 1430
    cell.save()
1385 1431
    chart = cell.get_chart()
1386
    request = new_api_mock.call['requests'][4]
1432
    request = new_api_mock.call['requests'][-1]
1387 1433
    assert 'start=2020-10-01' in request.url
1388 1434

  
1389 1435
    cell.time_range_end = '2020-11-03'
1390 1436
    cell.save()
1391 1437
    chart = cell.get_chart()
1392
    request = new_api_mock.call['requests'][5]
1438
    request = new_api_mock.call['requests'][-1]
1393 1439
    assert 'start=2020-10-01' in request.url and 'end=2020-11-03' in request.url
1394 1440

  
1395 1441

  
......
1435 1481
    assert time_interval_field.value == 'day'
1436 1482
    assert time_interval_field.options == [
1437 1483
        ('day', True, 'Day'),
1484
        ('_week', False, 'Week'),
1438 1485
        ('_month', False, 'Month'),
1439 1486
        ('_year', False, 'Year'),
1440 1487
        ('_weekday', False, 'Week day'),
......
1486 1533
        ([16, 3, 0, 0, 0, 0, 0], {'title': 'Serie 1'}),
1487 1534
        ([1, 4, 0, 0, 0, 0, 0], {'title': 'Serie 2'}),
1488 1535
    ]
1536

  
1537
    time_interval_field.value = '_week'
1538
    resp.form.submit()
1539
    cell.refresh_from_db()
1540

  
1541
    chart = cell.get_chart()
1542
    assert 'time_interval=day' in new_api_mock.call['requests'][1].url
1543
    assert len(chart.x_labels) == 70
1544
    assert chart.x_labels[:3] == ['W41-2020', 'W42-2020', 'W43-2020']
1545
    assert chart.x_labels[-6:] == ['W52-2021', 'W1-2022', 'W2-2022', 'W3-2022', 'W4-2022', 'W5-2022']
1546
    assert chart.raw_series == [
1547
        ([0, 1, 0, 0, 0, 0, 0, 0, 16] + [0] * 60 + [2], {'title': 'Serie 1'}),
1548
        ([2, 2, 0, 0, 0, 0, 0, 0, 1] + [0] * 61, {'title': 'Serie 2'}),
1549
    ]
1550

  
1551
    cell.statistic = Statistic.objects.get(slug='leap-week')
1552
    cell.save()
1553
    resp = app.get('/manage/pages/%s/' % page.id)
1554
    resp.form.submit()
1555
    chart = cell.get_chart()
1556
    assert 'time_interval=day' in new_api_mock.call['requests'][1].url
1557
    assert len(chart.x_labels) == 1
1558
    assert chart.x_labels == ['W53-2020']
1559
    assert chart.raw_series == [([19], {'title': 'Serie 1'})]
1489
-