Project

General

Profile

0001-dataviz-add-support-for-subfilters-61083.patch

Valentin Deniaud, 07 February 2022 06:04 PM

Download (9.39 KB)

View differences:

Subject: [PATCH] dataviz: add support for subfilters (#61083)

 combo/apps/dataviz/forms.py                   |  17 ++-
 .../migrations/0022_chartngcell_subfilters.py |  19 ++++
 combo/apps/dataviz/models.py                  |  20 ++++
 combo/utils/spooler.py                        |   1 +
 tests/test_dataviz.py                         | 105 ++++++++++++++++++
 5 files changed, 158 insertions(+), 4 deletions(-)
 create mode 100644 combo/apps/dataviz/migrations/0022_chartngcell_subfilters.py
combo/apps/dataviz/forms.py
65 65

  
66 66
    def get_filter_fields(self, cell):
67 67
        fields = OrderedDict()
68
        for filter_ in cell.statistic.filters:
68
        for filter_ in cell.available_filters:
69 69
            filter_id = filter_['id']
70 70
            choices = [(option['id'], option['label']) for option in filter_['options']]
71 71
            initial = cell.filter_params.get(filter_id, filter_.get('default'))
......
147 147
        if 'statistic' in self.changed_data:
148 148
            self.instance.filter_params.clear()
149 149
            self.instance.time_range = ''
150
            for filter_ in self.instance.statistic.filters:
150
            for filter_ in self.instance.available_filters:
151 151
                if 'default' in filter_:
152 152
                    self.instance.filter_params[filter_['id']] = filter_['default']
153 153
        else:
154
            for filter_ in self.instance.statistic.filters:
154
            for filter_ in self.instance.available_filters:
155 155
                self.instance.filter_params[filter_['id']] = self.cleaned_data.get(filter_['id'])
156
        return super().save(*args, **kwargs)
156

  
157
        cell = super().save(*args, **kwargs)
158

  
159
        for filter_ in cell.available_filters:
160
            if filter_.get('has_subfilters') and filter_['id'] in self.changed_data:
161
                cell.update_subfilters()
162
                self.__init__()
163
                break
164

  
165
        return cell
157 166

  
158 167
    def clean(self):
159 168
        for template_field in ('time_range_start_template', 'time_range_end_template'):
combo/apps/dataviz/migrations/0022_chartngcell_subfilters.py
1
# Generated by Django 2.2.19 on 2022-01-25 16:21
2

  
3
import django.contrib.postgres.fields.jsonb
4
from django.db import migrations
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('dataviz', '0021_chartfilterscell'),
11
    ]
12

  
13
    operations = [
14
        migrations.AddField(
15
            model_name='chartngcell',
16
            name='subfilters',
17
            field=django.contrib.postgres.fields.jsonb.JSONField(default=list),
18
        ),
19
    ]
combo/apps/dataviz/models.py
186 186
            'This list may take a few seconds to be updated, please refresh the page if an item is missing.'
187 187
        ),
188 188
    )
189
    subfilters = JSONField(default=list)
189 190
    filter_params = JSONField(default=dict)
190 191
    title = models.CharField(_('Title'), max_length=150, blank=True)
191 192
    time_range = models.CharField(
......
679 680
        for i, serie in enumerate(data['series']):
680 681
            serie['data'] = [values[i] for values in aggregates.values()]
681 682

  
683
    @property
684
    def available_filters(self):
685
        return self.statistic.filters + self.subfilters
686

  
687
    def update_subfilters(self):
688
        response = self.get_statistic_data()
689
        try:
690
            response.raise_for_status()
691
            data = response.json()['data']
692
        except:
693
            return
694

  
695
        new_subfilters = data.get('subfilters', [])
696
        if self.subfilters != new_subfilters:
697
            self.subfilters = new_subfilters
698
            subfilter_ids = {filter_['id'] for filter_ in self.available_filters}
699
            self.filter_params = {k: v for k, v in self.filter_params.items() if k in subfilter_ids}
700
            self.save()
701

  
682 702

  
683 703
@register_cell_class
684 704
class ChartFiltersCell(CellBase):
combo/utils/spooler.py
102 102
    except ChartNgCell.DoesNotExist:
103 103
        return
104 104
    cell.get_statistic_data(invalidate_cache=True)
105
    cell.update_subfilters()
tests/test_dataviz.py
407 407
                }
408 408
            ],
409 409
        },
410
        {
411
            'url': 'https://authentic.example.com/api/statistics/with-subfilter/',
412
            'name': 'With subfilter',
413
            'id': 'with-subfilter',
414
            'filters': [
415
                {
416
                    'id': 'form',
417
                    'label': 'Form',
418
                    'has_subfilters': True,
419
                    'options': [
420
                        {'id': 'food-request', 'label': 'Food request'},
421
                        {'id': 'contact', 'label': 'Contact'},
422
                        {'id': 'error', 'label': 'Error'},
423
                    ],
424
                },
425
                {
426
                    'id': 'other',
427
                    'label': 'Other',
428
                    'options': [
429
                        {'id': 'one', 'label': 'One'},
430
                        {'id': 'two', 'label': 'two'},
431
                    ],
432
                },
433
            ],
434
        },
410 435
    ]
411 436
}
412 437

  
......
473 498
            },
474 499
        }
475 500
        return {'content': json.dumps(response), 'request': request, 'status_code': 200}
501
    if url.path == '/api/statistics/with-subfilter/':
502
        response = {
503
            'data': {
504
                'series': [{'data': [None, 16, 2], 'label': 'Serie 1'}],
505
                'x_labels': ['2020-10', '2020-11', '2020-12'],
506
                'subfilters': [],
507
            },
508
        }
509
        if 'form=food-request' in url.query:
510
            response['data']['subfilters'] = [
511
                {
512
                    "id": "menu",
513
                    "label": "Menu",
514
                    "options": [
515
                        {"id": "meat", "label": "Meat"},
516
                        {"id": "vegan", "label": "Vegan"},
517
                    ],
518
                }
519
            ]
520
        if 'form=error' in url.query:
521
            return {'content': b'', 'request': request, 'status_code': 404}
522
        return {'content': json.dumps(response), 'request': request, 'status_code': 200}
476 523

  
477 524

  
478 525
@pytest.fixture
......
1296 1343
    assert cell.get_filter_params() == {}
1297 1344

  
1298 1345

  
1346
@with_httmock(new_api_mock)
1347
def test_chartng_cell_manager_subfilters(app, admin_user, new_api_statistics):
1348
    page = Page.objects.create(title='One', slug='index')
1349
    cell = ChartNgCell(page=page, order=1, placeholder='content')
1350
    cell.statistic = Statistic.objects.get(slug='with-subfilter')
1351
    cell.save()
1352

  
1353
    app = login(app)
1354
    resp = app.get('/manage/pages/%s/' % page.id)
1355
    field_prefix = 'cdataviz_chartngcell-%s-' % cell.id
1356

  
1357
    # choice with no subfilter
1358
    resp.form[field_prefix + 'form'] = 'contact'
1359
    resp = resp.form.submit().follow()
1360

  
1361
    assert len(new_api_mock.call['requests']) == 1
1362
    assert 'menu' not in resp.form.fields
1363

  
1364
    resp.form[field_prefix + 'form'] = 'error'
1365
    resp = resp.form.submit().follow()
1366

  
1367
    assert len(new_api_mock.call['requests']) == 2
1368
    assert 'menu' not in resp.form.fields
1369

  
1370
    # choice with subfilter
1371
    resp.form[field_prefix + 'form'] = 'food-request'
1372
    resp = resp.form.submit().follow()
1373

  
1374
    assert len(new_api_mock.call['requests']) == 3
1375
    menu_field = resp.form[field_prefix + 'menu']
1376
    assert menu_field.value == ''
1377
    assert menu_field.options == [
1378
        ('', True, '---------'),
1379
        ('meat', False, 'Meat'),
1380
        ('vegan', False, 'Vegan'),
1381
    ]
1382

  
1383
    resp.form[field_prefix + 'menu'] = 'meat'
1384
    resp = resp.form.submit().follow()
1385
    assert resp.form[field_prefix + 'menu'].value == 'meat'
1386
    cell.refresh_from_db()
1387
    assert cell.get_filter_params() == {'form': 'food-request', 'menu': 'meat'}
1388

  
1389
    # choice with no subfilter
1390
    resp.form[field_prefix + 'form'] = 'contact'
1391
    resp = resp.form.submit().follow()
1392

  
1393
    assert len(new_api_mock.call['requests']) == 4
1394
    assert 'menu' not in resp.form.fields
1395
    cell.refresh_from_db()
1396
    assert cell.get_filter_params() == {'form': 'contact'}
1397

  
1398
    # changing another filter doesn't trigger request
1399
    resp.form[field_prefix + 'other'] = 'one'
1400
    resp = resp.form.submit().follow()
1401
    assert len(new_api_mock.call['requests']) == 4
1402

  
1403

  
1299 1404
@with_httmock(new_api_mock)
1300 1405
@pytest.mark.freeze_time('2021-10-06')
1301 1406
def test_chartng_cell_manager_new_api_time_range_templates(app, admin_user, new_api_statistics):
1302
-