Projet

Général

Profil

0003-dataviz-save-available-visualizations-in-db-48865.patch

Valentin Deniaud, 01 décembre 2020 17:01

Télécharger (34,1 ko)

Voir les différences:

Subject: [PATCH 3/4] dataviz: save available visualizations in db (#48865)

 combo/apps/dataviz/__init__.py                |  32 +++
 combo/apps/dataviz/forms.py                   |  27 +-
 .../migrations/0012_auto_20201126_1557.py     |  42 ++++
 .../migrations/0013_update_chartng_cells.py   |  40 +++
 .../migrations/0014_auto_20201130_1534.py     |  23 ++
 combo/apps/dataviz/models.py                  |  60 +++--
 .../dataviz/templates/combo/chartngcell.html  |   4 +-
 .../templates/combo/chartngcell_form.html     |   2 +-
 combo/apps/dataviz/views.py                   |   2 +-
 tests/test_dataviz.py                         | 238 ++++++++++++------
 10 files changed, 344 insertions(+), 126 deletions(-)
 create mode 100644 combo/apps/dataviz/migrations/0012_auto_20201126_1557.py
 create mode 100644 combo/apps/dataviz/migrations/0013_update_chartng_cells.py
 create mode 100644 combo/apps/dataviz/migrations/0014_auto_20201130_1534.py
combo/apps/dataviz/__init__.py
19 19
import django.apps
20 20
from django.core import checks
21 21
from django.conf import settings
22
from django.utils import timezone
22 23
from django.utils.translation import ugettext_lazy as _
23 24

  
25
from combo.utils import requests
26

  
24 27

  
25 28
class AppConfig(django.apps.AppConfig):
26 29
    name = 'combo.apps.dataviz'
......
30 33
        from . import urls
31 34
        return urls.urlpatterns
32 35

  
36
    def hourly(self):
37
        self.update_available_statistics()
38

  
39
    def update_available_statistics(self):
40
        from .models import Statistic
41
        if not settings.KNOWN_SERVICES:
42
            return
43

  
44
        start_update = timezone.now()
45
        bijoe_sites = settings.KNOWN_SERVICES.get('bijoe').items()
46
        for site_key, site_dict in bijoe_sites:
47
            result = requests.get('/visualization/json/',
48
                    remote_service=site_dict, without_user=True,
49
                    headers={'accept': 'application/json'}).json()
50
            for stat in result:
51
                Statistic.objects.update_or_create(
52
                    slug=stat['slug'],
53
                    site_slug=site_key,
54
                    service_slug='bijoe',
55
                    defaults={
56
                        'label': stat['name'],
57
                        'url': stat['data-url'],
58
                        'site_title': site_dict.get('title', ''),
59
                        'available': True,
60
                    }
61
                )
62
        Statistic.objects.filter(last_update__lt=start_update).update(available=False)
63

  
64

  
33 65
default_app_config = 'combo.apps.dataviz.AppConfig'
combo/apps/dataviz/forms.py
16 16

  
17 17
from django import forms
18 18
from django.conf import settings
19
from django.db.models import Q
19 20
from django.utils.translation import ugettext_lazy as _
20 21

  
21 22
from combo.utils import requests
22 23

  
23
from .models import ChartCell, ChartNgCell
24
from .models import ChartCell, ChartNgCell, Statistic
24 25

  
25 26

  
26 27
class ChartForm(forms.ModelForm):
......
43 44
class ChartNgForm(forms.ModelForm):
44 45
    class Meta:
45 46
        model = ChartNgCell
46
        fields = ('title', 'data_reference', 'chart_type', 'height', 'sort_order',
47
        fields = ('title', 'statistic', 'chart_type', 'height', 'sort_order',
47 48
                  'hide_null_values')
48 49

  
49 50
    def __init__(self, *args, **kwargs):
50
        super(ChartNgForm, self).__init__(*args, **kwargs)
51
        data_references = []
52
        bijoe_sites = settings.KNOWN_SERVICES.get('bijoe').items()
53
        for site_key, site_dict in bijoe_sites:
54
            result = requests.get('/visualization/json/',
55
                    remote_service=site_dict, without_user=True,
56
                    headers={'accept': 'application/json'}).json()
57
            if len(bijoe_sites) > 1:
58
                label_prefix = _('%s: ') % site_dict.get('title')
59
            else:
60
                label_prefix = ''
61
            data_references.extend([
62
                ('%s:%s' % (site_key, x['slug']), '%s%s' % (label_prefix, x['name']))
63
                for x in result])
64

  
65
        data_references.sort(key=lambda x: x[1])
66
        self.fields['data_reference'].widget = forms.Select(choices=data_references)
51
        super().__init__(*args, **kwargs)
52
        q_filters = Q(available=True)
53
        if self.instance.statistic:
54
            q_filters |= Q(pk=self.instance.statistic.pk)
55
        self.fields['statistic'].queryset = self.fields['statistic'].queryset.filter(q_filters)
combo/apps/dataviz/migrations/0012_auto_20201126_1557.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.29 on 2020-11-26 14:57
3
from __future__ import unicode_literals
4

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

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('dataviz', '0011_auto_20200813_1100'),
13
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='Statistic',
18
            fields=[
19
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
                ('slug', models.SlugField(max_length=256, verbose_name='Slug')),
21
                ('label', models.CharField(max_length=256, verbose_name='Label')),
22
                ('site_slug', models.SlugField(max_length=256, verbose_name='Site slug')),
23
                ('service_slug', models.SlugField(max_length=256, verbose_name='Service slug')),
24
                ('site_title', models.CharField(max_length=256, verbose_name='Site title')),
25
                ('url', models.URLField(verbose_name='Data URL')),
26
                ('last_update', models.DateTimeField(auto_now=True, null=True, verbose_name='Last update')),
27
                ('available', models.BooleanField(default=True, verbose_name='Available data')),
28
            ],
29
            options={
30
                'ordering': ['-available', 'site_title', 'label'],
31
            },
32
        ),
33
        migrations.AlterUniqueTogether(
34
            name='statistic',
35
            unique_together=set([('slug', 'site_slug', 'service_slug')]),
36
        ),
37
        migrations.AddField(
38
            model_name='chartngcell',
39
            name='statistic',
40
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cells', to='dataviz.Statistic', verbose_name='Data'),
41
        ),
42
    ]
combo/apps/dataviz/migrations/0013_update_chartng_cells.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.29 on 2020-11-30 14:26
3
from __future__ import unicode_literals
4

  
5
from django.conf import settings
6
from django.db import migrations
7

  
8

  
9
def update_cells(apps, schema_editor):
10
    Statistic = apps.get_model('dataviz', 'Statistic')
11
    ChartNgCell = apps.get_model('dataviz', 'ChartNgCell')
12
    bijoe_sites = settings.KNOWN_SERVICES.get('bijoe', {})
13

  
14
    for cell in ChartNgCell.objects.filter(statistic__isnull=True):
15
        if not cell.data_reference or not cell.cached_json:
16
            continue
17
        site_slug, slug = cell.data_reference.split(':')
18
        statistic, _ = Statistic.objects.get_or_create(
19
            slug=slug,
20
            site_slug=site_slug,
21
            service_slug='bijoe',
22
            defaults={
23
                'label': cell.cached_json['name'],
24
                'url': cell.cached_json['data-url'],
25
                'site_title': bijoe_sites.get(site_slug, {}).get('title'),
26
            }
27
        )
28
        cell.statistic = statistic
29
        cell.save()
30

  
31

  
32
class Migration(migrations.Migration):
33

  
34
    dependencies = [
35
        ('dataviz', '0012_auto_20201126_1557'),
36
    ]
37

  
38
    operations = [
39
        migrations.RunPython(update_cells, migrations.RunPython.noop),
40
    ]
combo/apps/dataviz/migrations/0014_auto_20201130_1534.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.29 on 2020-11-30 14:34
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    dependencies = [
11
        ('dataviz', '0013_update_chartng_cells'),
12
    ]
13

  
14
    operations = [
15
        migrations.RemoveField(
16
            model_name='chartngcell',
17
            name='cached_json',
18
        ),
19
        migrations.RemoveField(
20
            model_name='chartngcell',
21
            name='data_reference',
22
        ),
23
    ]
combo/apps/dataviz/models.py
103 103
        return context
104 104

  
105 105

  
106
class StatisticManager(models.Manager):
107
    def get_by_natural_key(self, slug, site_slug, service_slug):
108
        return self.get_or_create(slug=slug, site_slug=site_slug, service_slug=service_slug)[0]
109

  
110

  
111
class Statistic(models.Model):
112
    slug = models.SlugField(_('Slug'), max_length=256)
113
    label = models.CharField(_('Label'), max_length=256)
114
    site_slug = models.SlugField(_('Site slug'), max_length=256)
115
    service_slug = models.SlugField(_('Service slug'), max_length=256)
116
    site_title = models.CharField(_('Site title'), max_length=256)
117
    url = models.URLField(_('Data URL'))
118
    available = models.BooleanField(_('Available data'), default=True)
119
    last_update = models.DateTimeField(_('Last update'), null=True, auto_now=True)
120

  
121
    objects = StatisticManager()
122

  
123
    class Meta:
124
        ordering = ['-available', 'site_title', 'label']
125
        unique_together = ['slug', 'site_slug', 'service_slug']
126

  
127
    def __str__(self):
128
        name = _('%s: %s') % (self.site_title, self.label)
129
        if not self.available:
130
            name = _('%s (unavailable)') % name
131
        return name
132

  
133
    def natural_key(self):
134
        return (self.slug, self.site_slug, self.service_slug)
135

  
136

  
106 137
@register_cell_class
107 138
class ChartNgCell(CellBase):
108
    data_reference = models.CharField(_('Data'), max_length=150)
139
    statistic = models.ForeignKey(
140
        verbose_name=_('Data'), to=Statistic, blank=False, null=True, on_delete=models.SET_NULL, related_name='cells'
141
    )
109 142
    title = models.CharField(_('Title'), max_length=150, blank=True)
110
    cached_json = JSONField(blank=True)
111 143
    chart_type = models.CharField(_('Chart Type'), max_length=20, default='bar',
112 144
            choices=(
113 145
                ('bar', _('Bar')),
......
154 186
    def get_additional_label(self):
155 187
        return self.title
156 188

  
157
    def is_relevant(self, context=None):
158
        return bool(self.data_reference)
159

  
160
    def save(self, *args, **kwargs):
161
        if self.data_reference:
162
            site_key, visualization_slug = self.data_reference.split(':')
163
            site_dict = settings.KNOWN_SERVICES['bijoe'][site_key]
164
            response_json = requests.get('/visualization/json/',
165
                    remote_service=site_dict, without_user=True,
166
                    headers={'accept': 'application/json'}).json()
167
            if isinstance(response_json, dict):
168
                # forward compatibility with possible API change
169
                response_json = response_json.get('data')
170
            for visualization in response_json:
171
                slug = visualization.get('slug')
172
                if slug == visualization_slug:
173
                    self.cached_json = visualization
174
        return super(ChartNgCell, self).save(*args, **kwargs)
189
    def is_relevant(self, context):
190
        return bool(self.statistic)
175 191

  
176 192
    def get_cell_extra_context(self, context):
177 193
        ctx = super(ChartNgCell, self).get_cell_extra_context(context)
178
        if self.chart_type == 'table' and self.cached_json:
194
        if self.chart_type == 'table' and self.statistic:
179 195
            try:
180 196
                chart = self.get_chart(raise_if_not_cached=not(context.get('synchronous')))
181 197
            except UnsupportedDataSet:
......
193 209

  
194 210
    def get_chart(self, width=None, height=None, raise_if_not_cached=False):
195 211
        response = requests.get(
196
                self.cached_json['data-url'],
212
                self.statistic.url,
197 213
                cache_duration=300,
198 214
                raise_if_not_cached=raise_if_not_cached)
199 215
        response.raise_for_status()
combo/apps/dataviz/templates/combo/chartngcell.html
1 1
{% load i18n %}
2 2
{% if cell.title %}<h2>{{cell.title}}</h2>{% endif %}
3
{% if not cell.cached_json %}
4
<div class="warningnotice">{% trans "Unavailable data." %}</div>
5
{% elif cell.chart_type == "table" %}
3
{% if cell.chart_type == "table" %}
6 4
{{table|safe}}
7 5
{% else %}
8 6
<div style="min-height: {{cell.height}}px">
combo/apps/dataviz/templates/combo/chartngcell_form.html
1 1
<div style="position: relative">
2 2
{{ form.as_p }}
3
{% if cell.cached_json and cell.chart_type != "table" and cell.is_relevant %}
3
{% if cell.statistic and cell.chart_type != "table" %}
4 4
<div style="position: absolute; right: 0; top: 0; width: 300px; height: 150px">
5 5
  <embed type="image/svg+xml" src="{% url 'combo-dataviz-graph' cell=cell.id %}?width=300&height=150"/>
6 6
</div>
combo/apps/dataviz/views.py
36 36
        raise PermissionDenied()
37 37
    if not cell.is_visible(user=request.user):
38 38
        raise PermissionDenied()
39
    if not cell.cached_json:
39
    if not cell.statistic:
40 40
        raise Http404('misconfigured cell')
41 41
    error_text = None
42 42
    try:
tests/test_dataviz.py
2 2

  
3 3
import mock
4 4
import pytest
5
from datetime import timedelta
5 6
from httmock import HTTMock, with_httmock
6 7
from requests.exceptions import HTTPError
7 8

  
8 9
from django.apps import apps
9 10
from django.contrib.auth.models import User, Group
11
from django.db import connection
12
from django.db.migrations.executor import MigrationExecutor
10 13
from django.test import override_settings
14
from django.utils import timezone
11 15

  
12 16
from combo.data.models import Page
13
from combo.apps.dataviz.models import Gauge, ChartNgCell, UnsupportedDataSet
17
from combo.apps.dataviz.models import Gauge, ChartNgCell, UnsupportedDataSet, Statistic
14 18

  
15 19
from .test_public import login, normal_user
16 20

  
......
224 228
        return {'content': json.dumps(response), 'request': request, 'status_code': 404}
225 229

  
226 230

  
231
@pytest.fixture
227 232
@with_httmock(bijoe_mock)
228
def test_chartng_cell(app):
233
def statistics():
234
    appconfig = apps.get_app_config('dataviz')
235
    appconfig.hourly()
236

  
237

  
238
@with_httmock(bijoe_mock)
239
def test_chartng_cell(app, statistics):
229 240
    page = Page(title='One', slug='index')
230 241
    page.save()
231 242

  
232 243
    cell = ChartNgCell(page=page, order=1)
233
    cell.data_reference = 'plop:example'
244
    cell.statistic = Statistic.objects.get(slug='example')
234 245
    cell.save()
235
    assert cell.cached_json == VISUALIZATION_JSON[0]
236 246

  
237 247
    # bar
238 248
    chart = cell.get_chart()
......
260 270

  
261 271
    # data in Y
262 272
    cell.chart_type = 'bar'
263
    cell.data_reference = 'plop:second'
273
    cell.statistic = Statistic.objects.get(slug='second')
264 274
    cell.save()
265
    assert cell.cached_json == VISUALIZATION_JSON[1]
266 275

  
267 276
    chart = cell.get_chart()
268 277
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
270 279

  
271 280
    # data in X/Y
272 281
    cell.chart_type = 'bar'
273
    cell.data_reference = 'plop:third'
282
    cell.statistic = Statistic.objects.get(slug='third')
274 283
    cell.save()
275
    assert cell.cached_json == VISUALIZATION_JSON[2]
276 284

  
277 285
    chart = cell.get_chart()
278 286
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
283 291

  
284 292
    # single data point
285 293
    cell.chart_type = 'bar'
286
    cell.data_reference = 'plop:fourth'
294
    cell.statistic = Statistic.objects.get(slug='fourth')
287 295
    cell.save()
288
    assert cell.cached_json == VISUALIZATION_JSON[3]
289 296

  
290 297
    chart = cell.get_chart()
291 298
    assert chart.x_labels == ['']
292 299
    assert chart.raw_series == [([222], {'title': ''})]
293 300

  
294 301
    # loop/X
295
    cell.data_reference = 'plop:fifth'
302
    cell.statistic = Statistic.objects.get(slug='fifth')
296 303
    cell.save()
297 304
    chart = cell.get_chart()
298 305
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
302 309
    ]
303 310

  
304 311
    # loop/Y
305
    cell.data_reference = 'plop:sixth'
312
    cell.statistic = Statistic.objects.get(slug='sixth')
306 313
    cell.save()
307 314
    chart = cell.get_chart()
308 315
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
312 319
    ]
313 320

  
314 321
    # loop/X/Y
315
    cell.data_reference = 'plop:seventh'
322
    cell.statistic = Statistic.objects.get(slug='seventh')
316 323
    cell.save()
317 324
    with pytest.raises(UnsupportedDataSet):
318 325
        chart = cell.get_chart()
319 326

  
320 327
    # duration
321
    cell.data_reference = 'plop:eighth'
328
    cell.statistic = Statistic.objects.get(slug='eighth')
322 329
    cell.save()
323 330
    chart = cell.get_chart()
324 331

  
325 332
    # loop/X/Y
326
    cell.data_reference = 'plop:nineth'
333
    cell.statistic = Statistic.objects.get(slug='nineth')
327 334
    cell.save()
328 335
    with pytest.raises(UnsupportedDataSet):
329 336
        chart = cell.get_chart()
330 337

  
331 338
    # deleted visualization
332
    cell.data_reference = 'plop:eleventh'
339
    cell.statistic = Statistic.objects.get(slug='eleventh')
333 340
    cell.save()
334 341
    with pytest.raises(HTTPError):
335 342
        chart = cell.get_chart()
336 343

  
337 344

  
338 345
@with_httmock(bijoe_mock)
339
def test_chartng_cell_hide_null_values(app):
346
def test_chartng_cell_hide_null_values(app, statistics):
340 347
    page = Page(title='One', slug='index')
341 348
    page.save()
342 349

  
343 350
    cell = ChartNgCell(page=page, order=1)
344
    cell.data_reference = 'plop:example'
351
    cell.statistic = Statistic.objects.get(slug='example')
345 352
    cell.hide_null_values = True
346 353
    cell.save()
347
    assert cell.cached_json == VISUALIZATION_JSON[0]
348 354

  
349 355
    # bar
350 356
    chart = cell.get_chart()
......
372 378

  
373 379
    # data in Y
374 380
    cell.chart_type = 'bar'
375
    cell.data_reference = 'plop:second'
381
    cell.statistic = Statistic.objects.get(slug='second')
376 382
    cell.save()
377
    assert cell.cached_json == VISUALIZATION_JSON[1]
378 383

  
379 384
    chart = cell.get_chart()
380 385
    assert chart.x_labels == ['web', 'mail', 'email']
......
382 387

  
383 388
    # data in X/Y
384 389
    cell.chart_type = 'bar'
385
    cell.data_reference = 'plop:third'
390
    cell.statistic = Statistic.objects.get(slug='third')
386 391
    cell.save()
387
    assert cell.cached_json == VISUALIZATION_JSON[2]
388 392

  
389 393
    chart = cell.get_chart()
390 394
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
395 399

  
396 400
    # single data point
397 401
    cell.chart_type = 'bar'
398
    cell.data_reference = 'plop:fourth'
402
    cell.statistic = Statistic.objects.get(slug='fourth')
399 403
    cell.save()
400
    assert cell.cached_json == VISUALIZATION_JSON[3]
401 404

  
402 405
    chart = cell.get_chart()
403 406
    assert chart.x_labels == ['']
404 407
    assert chart.raw_series == [([222], {'title': ''})]
405 408

  
406 409
    # loop/X
407
    cell.data_reference = 'plop:fifth'
410
    cell.statistic = Statistic.objects.get(slug='fifth')
408 411
    cell.save()
409 412
    chart = cell.get_chart()
410 413
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
414 417
    ]
415 418

  
416 419
    # loop/Y
417
    cell.data_reference = 'plop:sixth'
420
    cell.statistic = Statistic.objects.get(slug='sixth')
418 421
    cell.save()
419 422
    chart = cell.get_chart()
420 423
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
425 428

  
426 429

  
427 430
@with_httmock(bijoe_mock)
428
def test_chartng_cell_sort_order_alpha(app):
431
def test_chartng_cell_sort_order_alpha(app, statistics):
429 432
    page = Page(title='One', slug='index')
430 433
    page.save()
431 434

  
432 435
    cell = ChartNgCell(page=page, order=1)
433
    cell.data_reference = 'plop:example'
436
    cell.statistic = Statistic.objects.get(slug='example')
434 437
    cell.sort_order = 'alpha'
435 438
    cell.save()
436
    assert cell.cached_json == VISUALIZATION_JSON[0]
437 439

  
438 440
    # bar
439 441
    chart = cell.get_chart()
......
461 463

  
462 464
    # data in Y
463 465
    cell.chart_type = 'bar'
464
    cell.data_reference = 'plop:second'
466
    cell.statistic = Statistic.objects.get(slug='second')
465 467
    cell.save()
466
    assert cell.cached_json == VISUALIZATION_JSON[1]
467 468

  
468 469
    chart = cell.get_chart()
469 470
    assert chart.x_labels == ['email', 'mail', 'phone', 'web']
......
471 472

  
472 473
    # data in X/Y
473 474
    cell.chart_type = 'bar'
474
    cell.data_reference = 'plop:third'
475
    cell.statistic = Statistic.objects.get(slug='third')
475 476
    cell.save()
476
    assert cell.cached_json == VISUALIZATION_JSON[2]
477 477

  
478 478
    chart = cell.get_chart()
479 479
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
484 484

  
485 485
    # single data point
486 486
    cell.chart_type = 'bar'
487
    cell.data_reference = 'plop:fourth'
487
    cell.statistic = Statistic.objects.get(slug='fourth')
488 488
    cell.save()
489
    assert cell.cached_json == VISUALIZATION_JSON[3]
490 489

  
491 490
    chart = cell.get_chart()
492 491
    assert chart.x_labels == ['']
493 492
    assert chart.raw_series == [([222], {'title': ''})]
494 493

  
495 494
    # loop/X
496
    cell.data_reference = 'plop:fifth'
495
    cell.statistic = Statistic.objects.get(slug='fifth')
497 496
    cell.save()
498 497
    chart = cell.get_chart()
499 498
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
503 502
    ]
504 503

  
505 504
    # loop/Y
506
    cell.data_reference = 'plop:sixth'
505
    cell.statistic = Statistic.objects.get(slug='sixth')
507 506
    cell.save()
508 507
    chart = cell.get_chart()
509 508
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
514 513

  
515 514

  
516 515
@with_httmock(bijoe_mock)
517
def test_chartng_cell_sort_order_desc(app):
516
def test_chartng_cell_sort_order_desc(app, statistics):
518 517
    page = Page(title='One', slug='index')
519 518
    page.save()
520 519

  
521 520
    cell = ChartNgCell(page=page, order=1)
522
    cell.data_reference = 'plop:example'
521
    cell.statistic = Statistic.objects.get(slug='example')
523 522
    cell.sort_order = 'desc'
524 523
    cell.save()
525
    assert cell.cached_json == VISUALIZATION_JSON[0]
526 524

  
527 525
    # bar
528 526
    chart = cell.get_chart()
......
550 548

  
551 549
    # data in Y
552 550
    cell.chart_type = 'bar'
553
    cell.data_reference = 'plop:second'
551
    cell.statistic = Statistic.objects.get(slug='second')
554 552
    cell.save()
555
    assert cell.cached_json == VISUALIZATION_JSON[1]
556 553

  
557 554
    chart = cell.get_chart()
558 555
    assert chart.x_labels == ['web', 'mail', 'email', 'phone']
......
560 557

  
561 558
    # data in X/Y
562 559
    cell.chart_type = 'bar'
563
    cell.data_reference = 'plop:third'
560
    cell.statistic = Statistic.objects.get(slug='third')
564 561
    cell.save()
565
    assert cell.cached_json == VISUALIZATION_JSON[2]
566 562

  
567 563
    chart = cell.get_chart()
568 564
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
573 569

  
574 570
    # single data point
575 571
    cell.chart_type = 'bar'
576
    cell.data_reference = 'plop:fourth'
572
    cell.statistic = Statistic.objects.get(slug='fourth')
577 573
    cell.save()
578
    assert cell.cached_json == VISUALIZATION_JSON[3]
579 574

  
580 575
    chart = cell.get_chart()
581 576
    assert chart.x_labels == ['']
582 577
    assert chart.raw_series == [([222], {'title': ''})]
583 578

  
584 579
    # loop/X
585
    cell.data_reference = 'plop:fifth'
580
    cell.statistic = Statistic.objects.get(slug='fifth')
586 581
    cell.save()
587 582
    chart = cell.get_chart()
588 583
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
592 587
    ]
593 588

  
594 589
    # loop/Y
595
    cell.data_reference = 'plop:sixth'
590
    cell.statistic = Statistic.objects.get(slug='sixth')
596 591
    cell.save()
597 592
    chart = cell.get_chart()
598 593
    assert chart.x_labels == ['web', 'mail', 'phone', 'email']
......
603 598

  
604 599

  
605 600
@with_httmock(bijoe_mock)
606
def test_chartng_cell_view(app, normal_user):
601
def test_chartng_cell_view(app, normal_user, statistics):
607 602
    page = Page(title='One', slug='index')
608 603
    page.save()
609 604

  
610 605
    cell = ChartNgCell(page=page, order=1, placeholder='content')
611
    cell.data_reference = 'plop:example'
606
    cell.statistic = Statistic.objects.get(slug='example')
612 607
    cell.save()
613 608
    location = '/api/dataviz/graph/%s/' % cell.id
614 609
    resp = app.get(location)  # get data in cache
......
649 644
    assert '<td>222</td>' in resp.text
650 645

  
651 646
    # unsupported dataset
652
    cell.data_reference = 'plop:seventh'
647
    cell.statistic = Statistic.objects.get(slug='seventh')
653 648
    cell.save()
654 649
    resp = app.get(location)  # get data in cache
655 650
    resp = app.get('/')
......
661 656
    assert 'Unsupported dataset' in resp.text
662 657

  
663 658
    # durations
664
    cell.data_reference = 'plop:eighth'
659
    cell.statistic = Statistic.objects.get(slug='eighth')
665 660
    cell.chart_type = 'table'
666 661
    cell.save()
667 662
    resp = app.get(location)  # get data in cache
......
680 675
    assert '>1 day<' in resp.text
681 676

  
682 677
    # percents
683
    cell.data_reference = 'plop:tenth'
678
    cell.statistic = Statistic.objects.get(slug='tenth')
684 679
    cell.chart_type = 'table'
685 680
    cell.save()
686 681
    resp = app.get(location)  # get data in cache
......
693 688
    assert '>10.0%<' in resp.text
694 689

  
695 690
    # deleted visualization
696
    cell.data_reference = 'plop:eleventh'
691
    cell.statistic = Statistic.objects.get(slug='eleventh')
697 692
    cell.save()
698 693
    resp = app.get(location)
699 694
    assert 'not found' in resp.text
700 695

  
701
    # cell with missing cached_json (probably after import and missing
702
    # bijoe visualisation)
696
    # cell with no statistic chosen
703 697
    cell.chart_type = 'table'
698
    cell.statistic = None
704 699
    cell.save()
705
    ChartNgCell.objects.filter(id=cell.id).update(cached_json={})
706 700
    resp = app.get('/')
707
    assert 'warningnotice' in resp.text
701
    assert not 'cell' in resp.text
708 702

  
709 703

  
710 704
@with_httmock(bijoe_mock)
711
def test_chartng_cell_manager(app, admin_user):
705
def test_chartng_cell_manager(app, admin_user, statistics):
712 706
    page = Page(title='One', slug='index')
713 707
    page.save()
708
    Statistic.objects.create(
709
        slug='unavailable-stat', label='Unavailable Stat', site_slug='plop', available=False
710
    )
714 711

  
715 712
    app = login(app)
716 713

  
717 714
    cell = ChartNgCell(page=page, order=1, placeholder='content')
718
    cell.data_reference = 'plop:example'
715
    cell.statistic = Statistic.objects.get(slug='example')
719 716
    cell.save()
720 717
    resp = app.get('/manage/pages/%s/' % page.id)
721
    assert resp.form['cdataviz_chartngcell-%s-data_reference' % cell.id].options == [
722
        (u'plop:eighth', False, u'eighth visualization (duration)'),
723
        (u'plop:eleventh', False, u'eleventh visualization (not found)'),
724
        (u'plop:example', True, u'example visualization (X)'),
725
        (u'plop:fifth', False, u'fifth visualization (loop/X)'),
726
        (u'plop:fourth', False, u'fourth visualization (no axis)'),
727
        (u'plop:nineth', False, u'nineth visualization (loop over varying dimensions)'),
728
        (u'plop:second', False, u'second visualization (Y)'),
729
        (u'plop:seventh', False, u'seventh visualization (loop/X/Y)'),
730
        (u'plop:sixth', False, u'sixth visualization (loop/Y)'),
731
        (u'plop:tenth', False, u'tenth visualization (percents)'),
732
        (u'plop:third', False, u'third visualization (X/Y)'),
733
    ]
718
    statistics_field = resp.form['cdataviz_chartngcell-%s-statistic' % cell.id]
719
    assert len(statistics_field.options) == 12
720
    assert statistics_field.value == str(cell.statistic.pk)
721
    assert statistics_field.options[1][2] == 'test: eighth visualization (duration)'
722
    assert not 'Unavailable Stat' in resp.text
723

  
724
    cell.statistic = Statistic.objects.get(slug='unavailable-stat')
725
    cell.save()
726
    resp = app.get('/manage/pages/%s/' % page.id)
727
    statistics_field = resp.form['cdataviz_chartngcell-%s-statistic' % cell.id]
728
    assert len(statistics_field.options) == 13
729
    assert 'Unavailable Stat' in resp.text
734 730

  
735 731

  
736 732
@with_httmock(bijoe_mock)
737
def test_table_cell(app, admin_user):
733
def test_table_cell(app, admin_user, statistics):
738 734
    page = Page(title='One', slug='index')
739 735
    page.save()
740 736

  
741 737
    app = login(app)
742 738

  
743 739
    cell = ChartNgCell(page=page, order=1, placeholder='content')
744
    cell.data_reference = 'plop:example'
740
    cell.statistic = Statistic.objects.get(slug='example')
741
    cell.statistic = Statistic.objects.get(slug='example')
745 742
    cell.chart_type = 'table'
746 743
    cell.save()
747 744
    location = '/api/dataviz/graph/%s/' % cell.id
......
749 746
    resp = app.get('/')
750 747
    assert resp.text.count('Total') == 1
751 748

  
752
    cell.data_reference = 'plop:second'
749
    cell.statistic = Statistic.objects.get(slug='second')
753 750
    cell.save()
754 751
    resp = app.get(location)
755 752
    resp = app.get('/')
756 753
    assert resp.text.count('Total') == 1
757 754

  
758
    cell.data_reference = 'plop:third'
755
    cell.statistic = Statistic.objects.get(slug='third')
759 756
    cell.save()
760 757
    resp = app.get(location)
761 758
    resp = app.get('/')
762 759
    assert '114' in resp.text
763 760
    assert resp.text.count('Total') == 2
764 761

  
765
    cell.data_reference = 'plop:fourth'
762
    cell.statistic = Statistic.objects.get(slug='fourth')
766 763
    cell.save()
767 764
    resp = app.get(location)
768 765
    resp = app.get('/')
769 766
    assert resp.text.count('Total') == 0
770 767

  
771 768
    # total of durations is not computed
772
    cell.data_reference = 'plop:eigth'
769
    cell.statistic = Statistic.objects.get(slug='eighth')
773 770
    cell.save()
774 771
    resp = app.get(location)
775 772
    resp = app.get('/')
776 773
    assert resp.text.count('Total') == 0
774

  
775

  
776
def test_dataviz_hourly_unavailable_statistic(freezer, statistics):
777
    all_stats_count = Statistic.objects.count()
778
    assert Statistic.objects.filter(available=True).count() == all_stats_count
779

  
780
    def bijoe_mock_unavailable(url, request):
781
        visualization_json = VISUALIZATION_JSON[2:]
782
        return {'content': json.dumps(visualization_json), 'request': request, 'status_code': 200}
783

  
784
    freezer.move_to(timezone.now() + timedelta(hours=1))  # clear requests cache
785
    appconfig = apps.get_app_config('dataviz')
786
    with HTTMock(bijoe_mock_unavailable):
787
        appconfig.hourly()
788
    assert Statistic.objects.filter(available=True).count() == all_stats_count - 2
789

  
790

  
791
def test_dataviz_import_cell():
792
    page = Page.objects.create(title='One', slug='index')
793
    cell = ChartNgCell.objects.create(page=page, order=1, slug='test', placeholder='content')
794
    statistic = Statistic.objects.create(
795
        slug='example', site_slug='plop', service_slug='bijoe', url='https://example.org'
796
    )
797
    cell.statistic = statistic
798
    cell.save()
799

  
800
    site_export = [page.get_serialized_page()]
801
    cell.delete()
802

  
803
    Page.load_serialized_pages(site_export)
804
    cell = ChartNgCell.objects.get(slug='test')
805
    assert cell.statistic.pk == statistic.pk
806

  
807
    cell.delete()
808
    statistic.delete()
809

  
810
    Page.load_serialized_pages(site_export)
811
    cell = ChartNgCell.objects.get(slug='test')
812
    assert cell.statistic.slug == statistic.slug
813
    assert cell.statistic.site_slug == statistic.site_slug
814
    assert cell.statistic.service_slug == statistic.service_slug
815

  
816

  
817
def test_dataviz_cell_migration():
818
    page = Page.objects.create(title='One', slug='index')
819
    app = 'dataviz'
820

  
821
    migrate_from = [(app, '0012_auto_20201126_1557')]
822
    migrate_to = [(app, '0013_update_chartng_cells')]
823
    executor = MigrationExecutor(connection)
824
    old_apps = executor.loader.project_state(migrate_from).apps
825
    executor.migrate(migrate_from)
826

  
827
    Pagee = old_apps.get_model('data', 'Page')
828
    page = Pagee.objects.get(pk=page.pk)
829
    ChartNgCell = old_apps.get_model(app, 'ChartNgCell')
830
    cell = ChartNgCell.objects.create(
831
        page=page,
832
        order=1,
833
        data_reference='plop:example',
834
        cached_json={
835
            'data-url': 'https://bijoe.example.com/visualization/1/json/',
836
            'path': 'https://bijoe.example.com/visualization/1/iframe/?signature=123',
837
            'name': 'example visualization (X)',
838
            'slug': 'example',
839
        },
840
    )
841

  
842
    executor = MigrationExecutor(connection)
843
    executor.migrate(migrate_to)
844
    executor.loader.build_graph()
845

  
846
    apps = executor.loader.project_state(migrate_to).apps
847
    ChartNgCell = apps.get_model(app, 'ChartNgCell')
848
    cell = ChartNgCell.objects.get(pk=cell.pk)
849
    assert cell.statistic.slug == 'example'
850
    assert cell.statistic.label == 'example visualization (X)'
851
    assert cell.statistic.url == 'https://bijoe.example.com/visualization/1/json/'
852
    assert cell.statistic.site_slug == 'plop'
853
    assert cell.statistic.service_slug == 'bijoe'
854
    assert cell.statistic.site_title == 'test'
777
-