Projet

Général

Profil

0004-cells-w.c.s.-cells-invalid-report-38009.patch

Lauréline Guérin, 21 février 2020 11:14

Télécharger (22,7 ko)

Voir les différences:

Subject: [PATCH 4/6] cells: w.c.s. cells invalid report (#38009)

 combo/apps/wcs/__init__.py |   9 ++
 combo/apps/wcs/models.py   | 151 +++++++++++++++++++-----
 tests/test_wcs.py          | 232 +++++++++++++++++++++++++++++++++++++
 3 files changed, 361 insertions(+), 31 deletions(-)
combo/apps/wcs/__init__.py
61 61
        return urls.urlpatterns
62 62

  
63 63
    def hourly(self):
64
        from combo.data.library import get_cell_classes
65
        from combo.data.models import CellBase
66

  
67
        cell_classes = [c for c in self.get_models() if c in get_cell_classes()]
68
        for cell in CellBase.get_cells(cell_filter=lambda x: x in cell_classes, page__snapshot__isnull=True):
69
            if hasattr(cell, 'check_validity'):
70
                cell.check_validity()
71

  
64 72
        self.update_db_cache()
65 73

  
66 74
    def update_db_cache(self):
......
70 78
                cell_filter=lambda x: x in (WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell)):
71 79
            cell.save()
72 80

  
81

  
73 82
default_app_config = 'combo.apps.wcs.AppConfig'
combo/apps/wcs/models.py
19 19
import copy
20 20
import logging
21 21

  
22
from django import template
23 22
from django.conf import settings
24 23
from django.db import models
25 24
from django.forms import models as model_forms
......
34 33

  
35 34
from .utils import get_wcs_json, is_wcs_enabled, get_wcs_services
36 35

  
36

  
37
invalid_reason_codes = {
38
    'wcs_form_not_defined': _('No form set'),
39
    'wcs_form_not_found': _('Invalid form'),
40
    'wcs_category_not_defined': _('No category set'),
41
    'wcs_category_not_found': _('Invalid category'),
42
    'wcs_data_failure': _('Failed to get data'),
43
}
44

  
45

  
37 46
@register_cell_class
38 47
class WcsFormCell(CellBase):
39 48
    formdef_reference = models.CharField(_('Form'), max_length=150)
......
47 56
    add_link_label = _('New form link')
48 57
    edit_link_label = _('Edit form link')
49 58
    add_as_link_code = 'form-link'
59
    invalid_reason_codes = invalid_reason_codes
50 60

  
51 61
    is_enabled = classmethod(is_wcs_enabled)
52 62

  
......
58 68
        return WcsFormCellForm
59 69

  
60 70
    def save(self, *args, **kwargs):
61
        if self.formdef_reference:
62
            wcs_key, form_slug = self.formdef_reference.split(':')
63
            wcs_site = get_wcs_services().get(wcs_key)
64
            forms_response_json = get_wcs_json(wcs_site, 'api/formdefs/')
65
            if isinstance(forms_response_json, dict):
66
                # forward compability with future w.c.s. API
67
                forms_response_json = forms_response_json.get('data')
68
            for form in forms_response_json:
69
                slug = form.get('slug')
70
                if slug == form_slug:
71
                    self.cached_title = form.get('title')
72
                    self.cached_url = form.get('url')
73
                    self.cached_json = form
71
        if 'update_fields' in kwargs:
72
            # don't populate the cache
73
            return super(WcsFormCell, self).save(*args, **kwargs)
74

  
75
        def populate_cache():
76
            if self.formdef_reference:
77
                wcs_key, form_slug = self.formdef_reference.split(':')
78
                wcs_site = get_wcs_services().get(wcs_key)
79
                forms_response_json = get_wcs_json(wcs_site, 'api/formdefs/')
80

  
81
                if not forms_response_json:
82
                    # can not retrieve data, don't report cell as invalid
83
                    self.mark_as_valid(save=False)
84
                    return
85

  
86
                if isinstance(forms_response_json, dict):
87
                    # forward compability with future w.c.s. API
88
                    forms_response_json = forms_response_json.get('data')
89
                form_found = False
90
                for form in forms_response_json:
91
                    slug = form.get('slug')
92
                    if slug == form_slug:
93
                        self.cached_title = form.get('title')
94
                        self.cached_url = form.get('url')
95
                        self.cached_json = form
96
                        form_found = True
97
                        break
98
                if form_found:
99
                    self.mark_as_valid(save=False)
100
                else:
101
                    self.mark_as_invalid('wcs_form_not_found', save=False)
102
            else:
103
                self.mark_as_invalid('wcs_form_not_defined', save=False)
104

  
105
        populate_cache()
74 106
        return super(WcsFormCell, self).save(*args, **kwargs)
75 107

  
76 108
    def get_cell_extra_context(self, context):
......
129 161
    cached_description = models.TextField(_('Description'), blank=True)
130 162
    cached_url = models.URLField(_('Cached URL'))
131 163

  
164
    invalid_reason_codes = invalid_reason_codes
165

  
132 166
    class Meta:
133 167
        abstract = True
134 168

  
135 169
    def save(self, *args, **kwargs):
136
        if self.category_reference:
137
            wcs_key, category_slug = self.category_reference.split(':')
138
            wcs_site = get_wcs_services().get(wcs_key)
139
            categories_response_json = get_wcs_json(wcs_site, 'api/categories/')
140
            for category in categories_response_json.get('data'):
141
                slug = category.get('slug')
142
                if slug == category_slug:
143
                    self.cached_title = category.get('title')
144
                    self.cached_description = category.get('description') or ''
145
                    self.cached_url = category.get('url')
170
        if 'update_fields' in kwargs:
171
            # don't populate the cache
172
            return super(WcsCommonCategoryCell, self).save(*args, **kwargs)
173

  
174
        def populate_cache():
175
            if self.category_reference:
176
                wcs_key, category_slug = self.category_reference.split(':')
177
                wcs_site = get_wcs_services().get(wcs_key)
178
                categories_response_json = get_wcs_json(wcs_site, 'api/categories/')
179

  
180
                if not categories_response_json:
181
                    # can not retrieve data, don't report cell as invalid
182
                    return
183

  
184
                category_found = False
185
                for category in categories_response_json.get('data', []):
186
                    slug = category.get('slug')
187
                    if slug == category_slug:
188
                        self.cached_title = category.get('title')
189
                        self.cached_description = category.get('description') or ''
190
                        self.cached_url = category.get('url')
191
                        category_found = True
192
                        break
193
                if category_found:
194
                    self.mark_as_valid(save=False)
195
                else:
196
                    self.mark_as_invalid('wcs_category_not_found', save=False)
197
            else:
198
                self.mark_as_invalid('wcs_category_not_defined', save=False)
199

  
200
        populate_cache()
146 201
        return super(WcsCommonCategoryCell, self).save(*args, **kwargs)
147 202

  
148 203
    def get_additional_label(self):
......
192 247
    is_enabled = classmethod(is_wcs_enabled)
193 248
    cache_duration = 5
194 249
    api_url = None
195
    warn_on_404 = True
250
    warn_on_4xx = True
251
    invalid_reason_codes = invalid_reason_codes
196 252

  
197 253
    def get_api_url(self, context):
198 254
        return self.api_url
......
242 298
                for item in wcs_site['data']:
243 299
                    item['site_slug'] = wcs_slug
244 300

  
245
        if not 200 in returns: # not a single valid answer
246
            if returns != set([404]) or self.warn_on_404:
247
                logging.error('failed to get data from any %s (%r)', api_url, returns)
301
        if 200 not in returns:  # not a single valid answer
302
            logging.debug('failed to get data from any %s (%r)', api_url, returns)
303
            if all([400 <= r < 500 for r in returns]) and self.warn_on_4xx:
304
                # only 4xx errors, not a user cell, report the cell as invalid
305
                self.mark_as_invalid('wcs_data_failure', force=False)
306
            else:
307
                self.mark_as_valid()
308
        else:
309
            self.mark_as_valid()
248 310

  
249 311
        return wcs_sites
250 312

  
......
293 355

  
294 356

  
295 357
class WcsUserDataBaseCell(WcsDataBaseCell):
296
    warn_on_404 = False
358
    warn_on_4xx = False
297 359
    user_dependant = True
298 360

  
299 361
    class Meta:
......
305 367
        return super(WcsUserDataBaseCell, self).is_visible(user)
306 368

  
307 369

  
370
class CategoriesValidityMixin(object):
371
    def check_validity(self):
372
        categories = self.categories.get('data', [])
373

  
374
        if not categories:
375
            return
376

  
377
        for category_reference in categories:
378
            wcs_key, category_slug = category_reference.split(':')
379
            wcs_site = get_wcs_services().get(wcs_key)
380
            categories_response_json = get_wcs_json(wcs_site, 'api/categories/')
381

  
382
            if not categories_response_json:
383
                # can not retrieve data, don't report cell as invalid
384
                continue
385

  
386
            category_found = any(
387
                [c.get('slug') == category_slug
388
                 for c in categories_response_json.get('data', [])])
389
            if not category_found:
390
                self.mark_as_invalid('wcs_category_not_found')
391
                return
392

  
393
        self.mark_as_valid()
394

  
395

  
308 396
@register_cell_class
309
class WcsCurrentFormsCell(WcsUserDataBaseCell):
397
class WcsCurrentFormsCell(WcsUserDataBaseCell, CategoriesValidityMixin):
310 398
    variable_name = 'user_forms'
311 399

  
312 400
    categories = JSONField(_('Categories'), blank=True)
......
400 488

  
401 489
        return context
402 490

  
491

  
403 492
@register_cell_class
404
class WcsCurrentDraftsCell(WcsUserDataBaseCell):
493
class WcsCurrentDraftsCell(WcsUserDataBaseCell, CategoriesValidityMixin):
405 494
    variable_name = 'current_drafts'
406 495
    template_name = 'combo/wcs/current_drafts.html'
407 496

  
tests/test_wcs.py
22 22

  
23 23
import mock
24 24

  
25
from combo.data.library import get_cell_classes
25 26
from combo.data.models import CellBase, LinkListCell, Page
26 27
from combo.apps.search.engines import engines
27 28
from combo.apps.wcs.models import (WcsFormCell, WcsCurrentFormsCell,
......
299 300
    appconfig.update_db_cache()
300 301
    assert WcsFormCell.objects.get(id=cell.id).cached_title == 'form title'
301 302

  
303

  
304
@wcs_present
305
def test_form_cell_validity():
306
    page = Page.objects.create(title='xxx', slug='test_form_cell_save_cache', template_name='standard')
307
    cell = WcsFormCell.objects.create(page=page, placeholder='content', order=0)
308
    assert cell.invalid_reason_code == 'wcs_form_not_defined'
309
    assert cell.invalid_since is not None
310

  
311
    cell.formdef_reference = u'default:form-title'
312
    cell.save()
313
    assert cell.invalid_reason_code is None
314
    assert cell.invalid_since is None
315

  
316
    # can not retrieve formdefs, don't set cell as invalid
317
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
318
        mock_json = mock.Mock(status_code=404)
319
        requests_get.return_value = mock_json
320
        cell.save()
321
    assert cell.invalid_reason_code is None
322
    assert cell.invalid_since is None
323

  
324
    cell.formdef_reference = u'default:foobar'
325
    cell.save()
326
    assert cell.invalid_reason_code == 'wcs_form_not_found'
327
    assert cell.invalid_since is not None
328

  
329

  
302 330
@wcs_present
303 331
def test_form_cell_load():
304 332
    page = Page(title='xxx', slug='test_form_cell_save_cache', template_name='standard')
......
328 356
    assert cell.cached_title == 'Test 3'
329 357
    assert cell.get_additional_label() == 'Test 3'
330 358

  
359

  
360
@wcs_present
361
def test_category_cell_validity():
362
    page = Page.objects.create(title='xxx', slug='test_category_cell_save_cache', template_name='standard')
363
    cell = WcsCategoryCell.objects.create(page=page, placeholder='content', order=0)
364
    assert cell.invalid_reason_code == 'wcs_category_not_defined'
365
    assert cell.invalid_since is not None
366

  
367
    cell.category_reference = u'default:test-3'
368
    cell.save()
369
    assert cell.invalid_reason_code is None
370
    assert cell.invalid_since is None
371

  
372
    # can not retrieve categories, don't set cell as invalid
373
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
374
        mock_json = mock.Mock(status_code=404)
375
        requests_get.return_value = mock_json
376
        cell.save()
377
    assert cell.invalid_reason_code is None
378
    assert cell.invalid_since is None
379

  
380
    cell.category_reference = 'default:foobar'
381
    cell.save()
382
    assert cell.invalid_reason_code == 'wcs_category_not_found'
383
    assert cell.invalid_since is not None
384

  
385

  
331 386
@wcs_present
332 387
def test_form_cell_render():
333 388
    page = Page(title='xxx', slug='test_form_cell_render', template_name='standard')
......
426 481
    # check include drafts
427 482
    cell.include_drafts = False
428 483
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
484
        mock_json = mock.Mock(status_code=200)
485
        requests_get.return_value = mock_json
429 486
        cell.get_cell_extra_context(context)
430 487
    assert requests_get.call_args_list[0][0][0] == '/api/user/forms?limit=100&sort=desc'
431 488

  
432 489
    cell.include_drafts = True
433 490
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
491
        mock_json = mock.Mock(status_code=200)
492
        requests_get.return_value = mock_json
434 493
        cell.get_cell_extra_context(context)
435 494
    assert requests_get.call_args_list[0][0][0] == '/api/user/forms?limit=100&sort=desc&include-drafts=on'
436 495

  
437 496

  
497
@wcs_present
498
def test_current_forms_cell_validity(context):
499
    page = Page.objects.create(title='xxx', slug='test_current_forms_cell_render', template_name='standard')
500
    cell = WcsCurrentFormsCell.objects.create(page=page, placeholder='content', order=0)
501
    context['request'].user = MockUser()
502
    context['synchronous'] = True  # to get fresh content
503

  
504
    cell.get_data(context)
505
    assert cell.invalid_reason_code is None
506
    assert cell.invalid_since is None
507

  
508
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
509
        mock_json = mock.Mock(status_code=404)
510
        requests_get.return_value = mock_json
511
        cell.get_data(context)
512
    assert cell.invalid_reason_code is None  # error not reported
513
    assert cell.invalid_since is None
514

  
515

  
516
@wcs_present
517
def test_current_forms_cell_check_validity(context):
518
    page = Page.objects.create(title='xxx', slug='test_current_forms_cell_render', template_name='standard')
519
    cell = WcsCurrentFormsCell.objects.create(page=page, placeholder='content', order=0)
520

  
521
    # no category
522
    cell.check_validity()
523
    assert cell.invalid_reason_code is None
524
    assert cell.invalid_since is None
525

  
526
    # valid categories
527
    cell.categories = {'data': ['default:test-3', 'default:test-9']}
528
    cell.save()
529
    cell.check_validity()
530
    assert cell.invalid_reason_code is None
531
    assert cell.invalid_since is None
532

  
533
    # can not retrieve categories, don't set cell as invalid
534
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
535
        mock_json = mock.Mock(status_code=404)
536
        requests_get.return_value = mock_json
537
        cell.check_validity()
538
    assert cell.invalid_reason_code is None
539
    assert cell.invalid_since is None
540

  
541
    # invalid category
542
    cell.categories = {'data': ['default:foobar', 'default:test-9']}
543
    cell.save()
544
    cell.check_validity()
545
    assert cell.invalid_reason_code == 'wcs_category_not_found'
546
    assert cell.invalid_since is not None
547

  
548

  
438 549
@wcs_present
439 550
def test_current_forms_cell_render_single_site(context):
440 551
    page = Page(title='xxx', slug='test_current_forms_cell_render', template_name='standard')
......
542 653
    assert 'other' in data
543 654

  
544 655

  
656
@wcs_present
657
def test_care_forms_cell_validity(context):
658
    page = Page.objects.create(title='xxx', slug='test_care_forms_cell_render', template_name='standard')
659
    cell = WcsCareFormsCell.objects.create(page=page, placeholder='content', order=0)
660
    context['synchronous'] = True  # to get fresh content
661
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
662
        mock_json = mock.Mock(status_code=200)
663
        requests_get.return_value = mock_json
664
        cell.get_data(context)
665
    assert cell.invalid_reason_code is None
666
    assert cell.invalid_since is None
667

  
668
    # can not retrieve data, don't set cell as invalid
669
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
670
        mock_json = mock.Mock(status_code=500)
671
        requests_get.return_value = mock_json
672
        cell.get_data(context)
673
    assert cell.invalid_reason_code is None
674
    assert cell.invalid_since is None
675

  
676
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
677
        mock_json = mock.Mock(status_code=404)
678
        requests_get.return_value = mock_json
679
        cell.get_data(context)
680
    assert cell.invalid_reason_code == 'wcs_data_failure'
681
    assert cell.invalid_since is not None
682

  
683

  
545 684
@wcs_present
546 685
def test_care_forms_cell_render_single_site(context):
547 686
    page = Page(title='xxx', slug='test_care_forms_cell_render', template_name='standard')
......
579 718
            (u'other:test-3', u'test2 : Test 3'),
580 719
            (u'other:test-9', u'test2 : Test 9')]
581 720

  
721

  
582 722
@wcs_present
583 723
def test_forms_of_category_cell_render(context):
584 724
    page = Page(title='xxx', slug='test_forms_of_category_cell_render', template_name='standard')
......
625 765
    assert cell.render_for_search() == ''
626 766
    assert len(list(cell.get_external_links_data())) == 2
627 767

  
768

  
769
@wcs_present
770
def test_forms_of_category_cell_validity(context):
771
    page = Page.objects.create(title='xxx', slug='test_forms_of_category_cell_render', template_name='standard')
772
    cell = WcsFormsOfCategoryCell.objects.create(page=page, placeholder='content', order=0)
773
    assert cell.invalid_reason_code == 'wcs_category_not_defined'
774
    assert cell.invalid_since is not None
775
    cell.category_reference = 'default:test-9'
776
    cell.save()
777
    assert cell.invalid_reason_code is None
778
    assert cell.invalid_since is None
779

  
780
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
781
        mock_json = mock.Mock(status_code=404)
782
        requests_get.return_value = mock_json
783
        cell.render(context)
784
    assert cell.invalid_reason_code == 'wcs_data_failure'
785
    assert cell.invalid_since is not None
786
    cell.mark_as_valid()
787

  
788
    # can not retrieve data, don't set cell as invalid
789
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
790
        mock_json = mock.Mock(status_code=500)
791
        requests_get.return_value = mock_json
792
        cell.render(context)
793
    assert cell.invalid_reason_code is None
794
    assert cell.invalid_since is None
795

  
796
    # can not retrieve categories, don't set cell as invalid
797
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
798
        mock_json = mock.Mock(status_code=404)
799
        requests_get.return_value = mock_json
800
        cell.save()
801
    assert cell.invalid_reason_code is None
802
    assert cell.invalid_since is None
803

  
804
    cell.category_reference = 'default:foobar'
805
    cell.save()
806
    assert cell.invalid_reason_code == 'wcs_category_not_found'
807
    assert cell.invalid_since is not None
808

  
809

  
628 810
@wcs_present
629 811
def test_current_drafts_cell_render_unlogged(context):
630 812
    page = Page(title='xxx', slug='test_current_drafts_cell_render', template_name='standard')
......
666 848
    assert len(extra_context['drafts']) == 0
667 849

  
668 850

  
851
@wcs_present
852
def test_current_drafts_cell_check_validity(context):
853
    page = Page.objects.create(title='xxx', slug='test_current_forms_cell_render', template_name='standard')
854
    cell = WcsCurrentDraftsCell.objects.create(page=page, placeholder='content', order=0)
855

  
856
    # no category
857
    cell.check_validity()
858
    assert cell.invalid_reason_code is None
859
    assert cell.invalid_since is None
860

  
861
    # valid categories
862
    cell.categories = {'data': ['default:test-3', 'default:test-9']}
863
    cell.save()
864
    cell.check_validity()
865
    assert cell.invalid_reason_code is None
866
    assert cell.invalid_since is None
867

  
868
    # can not retrieve vategories, don't set cell as invalid
869
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
870
        mock_json = mock.Mock(status_code=404)
871
        requests_get.return_value = mock_json
872
        cell.check_validity()
873
    assert cell.invalid_reason_code is None
874
    assert cell.invalid_since is None
875

  
876
    # invalid category
877
    cell.categories = {'data': ['default:foobar', 'default:test-9']}
878
    cell.save()
879
    cell.check_validity()
880
    assert cell.invalid_reason_code == 'wcs_category_not_found'
881
    assert cell.invalid_since is not None
882

  
883

  
669 884
@wcs_present
670 885
def test_manager_forms_of_category_cell(app, admin_user):
671 886
    Page.objects.all().delete()
......
964 1179
    assert WcsFormCell.objects.count() == 0
965 1180

  
966 1181

  
1182
@wcs_present
967 1183
def test_import_export_pages_with_links():
968 1184
    page = Page(title=u'bar', slug='bar', order=1)
969 1185
    page.save()
......
991 1207
    assert new_item.cached_title == item.cached_title
992 1208
    assert new_item.cached_url == item.cached_url
993 1209
    assert new_item.cached_json == item.cached_json
1210

  
1211

  
1212
@wcs_present
1213
def test_hourly():
1214
    appconfig = apps.get_app_config('wcs')
1215
    page = Page.objects.create(title='xxx', slug='test_current_forms_cell_render', template_name='standard')
1216
    cell_classes = [c for c in appconfig.get_models() if c in get_cell_classes()]
1217
    for klass in cell_classes:
1218
        klass.objects.create(page=page, placeholder='content', order=0)
1219
    for klass in cell_classes:
1220
        if klass in [WcsCurrentFormsCell, WcsCurrentDraftsCell]:
1221
            with mock.patch('combo.apps.wcs.models.%s.check_validity' % klass.__name__) as check_validity:
1222
                appconfig.hourly()
1223
            assert check_validity.call_args_list == [mock.call()]
1224
        else:
1225
            assert hasattr(klass, 'check_validity') is False
994
-